diff --git a/autotests/integration/activities_test.cpp b/autotests/integration/activities_test.cpp index 1acfead39..91c238513 100644 --- a/autotests/integration/activities_test.cpp +++ b/autotests/integration/activities_test.cpp @@ -1,162 +1,162 @@ /******************************************************************** 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 "activities.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 "xcbutils.h" #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_activities-0"); class ActivitiesTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testSetOnActivitiesValidates(); private: }; void ActivitiesTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setUseKActivities(true); 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 ActivitiesTest::cleanupTestCase() { QProcess::execute(QStringLiteral("kactivitymanagerd"), QStringList{QStringLiteral("stop")}); } void ActivitiesTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void ActivitiesTest::cleanup() { } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void ActivitiesTest::testSetOnActivitiesValidates() { // this test creates a Client and sets it on activities which don't exist // that should result in the window being on all activities // 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(0, 0, 100, 200); auto cookie = xcb_create_window_checked(c.data(), 0, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, 0, 0, nullptr); QVERIFY(!xcb_request_check(c.data(), cookie)); 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()); //verify the test machine doesn't have the following activities used QVERIFY(!Activities::self()->all().contains(QStringLiteral("foo"))); QVERIFY(!Activities::self()->all().contains(QStringLiteral("bar"))); //setting the client to an invalid activities should result in the client being on all activities client->setOnActivity(QStringLiteral("foo"), true); QVERIFY(client->isOnAllActivities()); QVERIFY(!client->activities().contains(QLatin1String("foo"))); client->setOnActivities(QStringList{QStringLiteral("foo"), QStringLiteral("bar")}); QVERIFY(client->isOnAllActivities()); QVERIFY(!client->activities().contains(QLatin1String("foo"))); QVERIFY(!client->activities().contains(QLatin1String("bar"))); // 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::ActivitiesTest) #include "activities_test.moc" diff --git a/autotests/integration/colorcorrect_nightcolor_test.cpp b/autotests/integration/colorcorrect_nightcolor_test.cpp index ecc319c91..84571d6df 100644 --- a/autotests/integration/colorcorrect_nightcolor_test.cpp +++ b/autotests/integration/colorcorrect_nightcolor_test.cpp @@ -1,334 +1,334 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2017 Roman Gilg 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 "wayland_server.h" #include "colorcorrection/manager.h" #include "colorcorrection/constants.h" #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_colorcorrect_nightcolor-0"); class ColorCorrectNightColorTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testConfigRead_data(); void testConfigRead(); void testChangeConfiguration_data(); void testChangeConfiguration(); void testAutoLocationUpdate(); }; void ColorCorrectNightColorTest::initTestCase() { QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); - QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void ColorCorrectNightColorTest::init() { } void ColorCorrectNightColorTest::cleanup() { } void ColorCorrectNightColorTest::testConfigRead_data() { QTest::addColumn("active"); QTest::addColumn("mode"); QTest::addColumn("nightTemperature"); QTest::addColumn("latitudeFixed"); QTest::addColumn("longitudeFixed"); QTest::addColumn("morningBeginFixed"); QTest::addColumn("eveningBeginFixed"); QTest::addColumn("transitionTime"); QTest::addColumn("success"); QTest::newRow("activeMode0") << "true" << 0 << 4500 << 45.5 << 35.1 << "0600" << "1800" << 30 << true; QTest::newRow("activeMode1") << "true" << 1 << 2500 << -10.5 << -8. << "0020" << "2000" << 60 << true; QTest::newRow("notActiveMode2") << "false" << 2 << 5000 << 90. << -180. << "0600" << "1800" << 1 << true; QTest::newRow("wrongData1") << "fa" << 3 << 7000 << 91. << -181. << "060" << "800" << 999999 << false; QTest::newRow("wrongData2") << "fa" << 3 << 7000 << 91. << -181. << "060" << "800" << -2 << false; } void ColorCorrectNightColorTest::testConfigRead() { QFETCH(QString, active); QFETCH(int, mode); QFETCH(int, nightTemperature); QFETCH(double, latitudeFixed); QFETCH(double, longitudeFixed); QFETCH(QString, morningBeginFixed); QFETCH(QString, eveningBeginFixed); QFETCH(int, transitionTime); QFETCH(bool, success); const bool activeDefault = true; const int modeDefault = 0; const int nightTemperatureUpperEnd = ColorCorrect::NEUTRAL_TEMPERATURE; const double latitudeFixedDefault = 0; const double longitudeFixedDefault = 0; const QTime morningBeginFixedDefault = QTime(6,0,0); const QTime eveningBeginFixedDefault = QTime(18,0,0); const int transitionTimeDefault = 30; KConfigGroup cfgGroup = kwinApp()->config()->group("NightColor"); cfgGroup.writeEntry("Active", activeDefault); cfgGroup.writeEntry("Mode", modeDefault); cfgGroup.writeEntry("NightTemperature", nightTemperatureUpperEnd); cfgGroup.writeEntry("LatitudeFixed", latitudeFixedDefault); cfgGroup.writeEntry("LongitudeFixed", longitudeFixedDefault); cfgGroup.writeEntry("MorningBeginFixed", morningBeginFixedDefault.toString("hhmm")); cfgGroup.writeEntry("EveningBeginFixed", eveningBeginFixedDefault.toString("hhmm")); cfgGroup.writeEntry("TransitionTime", transitionTimeDefault); ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); manager->reparseConfigAndReset(); auto info = manager->info(); QVERIFY(!info.isEmpty()); QCOMPARE(info.value("Active").toBool(), activeDefault); QCOMPARE(info.value("Mode").toInt(), modeDefault); QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureUpperEnd); QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedDefault); QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedDefault); QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), morningBeginFixedDefault); QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), eveningBeginFixedDefault); QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeDefault); cfgGroup.writeEntry("Active", active); cfgGroup.writeEntry("Mode", mode); cfgGroup.writeEntry("NightTemperature", nightTemperature); cfgGroup.writeEntry("LatitudeFixed", latitudeFixed); cfgGroup.writeEntry("LongitudeFixed", longitudeFixed); cfgGroup.writeEntry("MorningBeginFixed", morningBeginFixed); cfgGroup.writeEntry("EveningBeginFixed", eveningBeginFixed); cfgGroup.writeEntry("TransitionTime", transitionTime); manager->reparseConfigAndReset(); info = manager->info(); QVERIFY(!info.isEmpty()); if (success) { QCOMPARE(info.value("Active").toBool() ? QString("true") : QString("false"), active); QCOMPARE(info.value("Mode").toInt(), mode); QCOMPARE(info.value("NightTemperature").toInt(), nightTemperature); QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixed); QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixed); QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), QTime::fromString(morningBeginFixed, "hhmm")); QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), QTime::fromString(eveningBeginFixed, "hhmm")); QCOMPARE(info.value("TransitionTime").toInt(), transitionTime); } else { QCOMPARE(info.value("Active").toBool(), activeDefault); QCOMPARE(info.value("Mode").toInt(), modeDefault); QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureUpperEnd); QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedDefault); QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedDefault); QCOMPARE(QTime::fromString(info.value("MorningBeginFixed").toString(), Qt::ISODate), morningBeginFixedDefault); QCOMPARE(QTime::fromString(info.value("EveningBeginFixed").toString(), Qt::ISODate), eveningBeginFixedDefault); QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeDefault); } } void ColorCorrectNightColorTest::testChangeConfiguration_data() { QTest::addColumn("activeReadIn"); QTest::addColumn("modeReadIn"); QTest::addColumn("nightTemperatureReadIn"); QTest::addColumn("latitudeFixedReadIn"); QTest::addColumn("longitudeFixedReadIn"); QTest::addColumn("morBeginFixedReadIn"); QTest::addColumn("eveBeginFixedReadIn"); QTest::addColumn("transitionTimeReadIn"); QTest::addColumn("successReadIn"); QTest::newRow("data0") << true << 0 << 4500 << 45.5 << 35.1 << QTime(6,0,0) << QTime(18,0,0) << 30 << true; QTest::newRow("data1") << true << 1 << 2500 << -10.5 << -8. << QTime(0,2,0) << QTime(20,0,0) << 60 << true; QTest::newRow("data2") << false << 2 << 5000 << 90. << -180. << QTime(6,0,0) << QTime(19,1,1) << 1 << true; QTest::newRow("wrongData0") << true << 3 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; QTest::newRow("wrongData1") << true << 0 << 500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; QTest::newRow("wrongData2") << true << 0 << 7000 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; QTest::newRow("wrongData3") << true << 0 << 4500 << 91. << -181. << QTime(6,0,0) << QTime(18,0,0) << 30 << false; QTest::newRow("wrongData4") << true << 0 << 4500 << 0. << 0. << QTime(18,0,0) << QTime(6,0,0) << 30 << false; QTest::newRow("wrongData5") << true << 0 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << 0 << false; QTest::newRow("wrongData6") << true << 0 << 4500 << 0. << 0. << QTime(6,0,0) << QTime(18,0,0) << -1 << false; QTest::newRow("wrongData7") << true << 0 << 4500 << 0. << 0. << QTime(12,0,0) << QTime(12,30,0) << 30 << false; QTest::newRow("wrongData8") << true << 0 << 4500 << 0. << 0. << QTime(1,0,0) << QTime(23,30,0) << 90 << false; } void ColorCorrectNightColorTest::testChangeConfiguration() { QFETCH(bool, activeReadIn); QFETCH(int, modeReadIn); QFETCH(int, nightTemperatureReadIn); QFETCH(double, latitudeFixedReadIn); QFETCH(double, longitudeFixedReadIn); QFETCH(QTime, morBeginFixedReadIn); QFETCH(QTime, eveBeginFixedReadIn); QFETCH(int, transitionTimeReadIn); QFETCH(bool, successReadIn); const bool activeDefault = true; const int modeDefault = 0; const int nightTemperatureDefault = ColorCorrect::DEFAULT_NIGHT_TEMPERATURE; const double latitudeFixedDefault = 0; const double longitudeFixedDefault = 0; const QTime morningBeginFixedDefault = QTime(6,0,0); const QTime eveningBeginFixedDefault = QTime(18,0,0); const int transitionTimeDefault = 30; // init with default values bool active = activeDefault; int mode = modeDefault; int nightTemperature = nightTemperatureDefault; double latitudeFixed = latitudeFixedDefault; double longitudeFixed = longitudeFixedDefault; QTime morningBeginFixed = morningBeginFixedDefault; QTime eveningBeginFixed = eveningBeginFixedDefault; int transitionTime = transitionTimeDefault; bool activeExpect = activeDefault; int modeExpect = modeDefault; int nightTemperatureExpect = nightTemperatureDefault; double latitudeFixedExpect = latitudeFixedDefault; double longitudeFixedExpect = longitudeFixedDefault; QTime morningBeginFixedExpect = morningBeginFixedDefault; QTime eveningBeginFixedExpect = eveningBeginFixedDefault; int transitionTimeExpect = transitionTimeDefault; QHash data; auto setData = [&active, &mode, &nightTemperature, &latitudeFixed, &longitudeFixed, &morningBeginFixed, &eveningBeginFixed, &transitionTime, &data]() { data["Active"] = active; data["Mode"] = mode; data["NightTemperature"] = nightTemperature; data["LatitudeFixed"] = latitudeFixed; data["LongitudeFixed"] = longitudeFixed; data["MorningBeginFixed"] = morningBeginFixed.toString(Qt::ISODate); data["EveningBeginFixed"] = eveningBeginFixed.toString(Qt::ISODate); data["TransitionTime"] = transitionTime; }; auto compareValues = [&activeExpect, &modeExpect, &nightTemperatureExpect, &latitudeFixedExpect, &longitudeFixedExpect, &morningBeginFixedExpect, &eveningBeginFixedExpect, &transitionTimeExpect](QHash info) { QCOMPARE(info.value("Active").toBool(), activeExpect); QCOMPARE(info.value("Mode").toInt(), modeExpect); QCOMPARE(info.value("NightTemperature").toInt(), nightTemperatureExpect); QCOMPARE(info.value("LatitudeFixed").toDouble(), latitudeFixedExpect); QCOMPARE(info.value("LongitudeFixed").toDouble(), longitudeFixedExpect); QCOMPARE(info.value("MorningBeginFixed").toString(), morningBeginFixedExpect.toString(Qt::ISODate)); QCOMPARE(info.value("EveningBeginFixed").toString(), eveningBeginFixedExpect.toString(Qt::ISODate)); QCOMPARE(info.value("TransitionTime").toInt(), transitionTimeExpect); }; ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); // test with default values setData(); manager->changeConfiguration(data); compareValues(manager->info()); // set to test values active = activeReadIn; mode = modeReadIn; nightTemperature = nightTemperatureReadIn; latitudeFixed = latitudeFixedReadIn; longitudeFixed = longitudeFixedReadIn; morningBeginFixed = morBeginFixedReadIn; eveningBeginFixed = eveBeginFixedReadIn; transitionTime = transitionTimeReadIn; if (successReadIn) { activeExpect = activeReadIn; modeExpect = modeReadIn; nightTemperatureExpect = nightTemperatureReadIn; latitudeFixedExpect = latitudeFixedReadIn; longitudeFixedExpect = longitudeFixedReadIn; morningBeginFixedExpect = morBeginFixedReadIn; eveningBeginFixedExpect = eveBeginFixedReadIn; transitionTimeExpect = transitionTimeReadIn; } // test with test values setData(); QCOMPARE(manager->changeConfiguration(data), successReadIn); compareValues(manager->info()); } void ColorCorrectNightColorTest::testAutoLocationUpdate() { ColorCorrect::Manager *manager = kwinApp()->platform()->colorCorrectManager(); auto info = manager->info(); QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); // wrong latitude value manager->autoLocationUpdate(91, 15); info = manager->info(); QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); // wrong longitude value manager->autoLocationUpdate(50, -181); info = manager->info(); QCOMPARE(info.value("LatitudeAuto").toDouble(), 0.); QCOMPARE(info.value("LongitudeAuto").toDouble(), 0.); // change manager->autoLocationUpdate(50, -180); info = manager->info(); QCOMPARE(info.value("LatitudeAuto").toDouble(), 50.); QCOMPARE(info.value("LongitudeAuto").toDouble(), -180.); // small deviation only manager->autoLocationUpdate(51.5, -179.5); info = manager->info(); QCOMPARE(info.value("LongitudeAuto").toDouble(), -180.); QCOMPARE(info.value("LatitudeAuto").toDouble(), 50.); } WAYLANDTEST_MAIN(ColorCorrectNightColorTest) #include "colorcorrect_nightcolor_test.moc" diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp index 3272abc47..399b6d0fb 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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()); // 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 e15c716e6..584d371ee 100644 --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -1,840 +1,840 @@ /******************************************************************** 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(); void testModifierClickUnrestrictedMove_data(); void testModifierClickUnrestrictedMove(); void testModifierScrollOpacity_data(); void testModifierScrollOpacity(); void testTouchEvents_data(); void testTouchEvents(); 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } 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|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } 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|xdgv5") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("top|xdgv5") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topRight|xdgv5") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("topLeft|xdgv6") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("top|xdgv6") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("topRight|xdgv6") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV6; } 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; } 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|xdgv5") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To left|xdgv5") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To bottom|xdgv5") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To top|xdgv5") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("To right|xdgv6") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To left|xdgv6") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To bottom|xdgv6") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("To top|xdgv6") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV6; } 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("xdgShellV6 - left") << Test::ShellSurfaceType::XdgShellV6 << 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("xdgShellV6 - right") << Test::ShellSurfaceType::XdgShellV6 << 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; QTest::newRow("xdgShellV6 - bottom") << Test::ShellSurfaceType::XdgShellV6 << 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()); } void DecorationInputTest::testModifierClickUnrestrictedMove_data() { QTest::addColumn("modifierKey"); QTest::addColumn("mouseButton"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); QTest::addColumn("surfaceType"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); const QVector> surfaceTypes{ {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, }; for (const auto &type: surfaceTypes) { QTest::newRow("Left Alt + Left Click" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << false << type.first; QTest::newRow("Left Alt + Right Click" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << false << type.first; QTest::newRow("Left Alt + Middle Click" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << false << type.first; QTest::newRow("Right Alt + Left Click" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << false << type.first; QTest::newRow("Right Alt + Right Click" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << false << type.first; QTest::newRow("Right Alt + Middle Click" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << false << type.first; // now everything with meta QTest::newRow("Left Meta + Left Click" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << false << type.first; QTest::newRow("Left Meta + Right Click" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << false << type.first; QTest::newRow("Left Meta + Middle Click" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << false << type.first; QTest::newRow("Right Meta + Left Click" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << false << type.first; QTest::newRow("Right Meta + Right Click" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << false << type.first; QTest::newRow("Right Meta + Middle Click" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << false << type.first; // and with capslock QTest::newRow("Left Alt + Left Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_LEFT << alt << true << type.first; QTest::newRow("Left Alt + Right Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_RIGHT << alt << true << type.first; QTest::newRow("Left Alt + Middle Click/CapsLock" + type.second) << KEY_LEFTALT << BTN_MIDDLE << alt << true << type.first; QTest::newRow("Right Alt + Left Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_LEFT << alt << true << type.first; QTest::newRow("Right Alt + Right Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_RIGHT << alt << true << type.first; QTest::newRow("Right Alt + Middle Click/CapsLock" + type.second) << KEY_RIGHTALT << BTN_MIDDLE << alt << true << type.first; // now everything with meta QTest::newRow("Left Meta + Left Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_LEFT << meta << true << type.first; QTest::newRow("Left Meta + Right Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_RIGHT << meta << true << type.first; QTest::newRow("Left Meta + Middle Click/CapsLock" + type.second) << KEY_LEFTMETA << BTN_MIDDLE << meta << true << type.first; QTest::newRow("Right Meta + Left Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_LEFT << meta << true << type.first; QTest::newRow("Right Meta + Right Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_RIGHT << meta << true << type.first; QTest::newRow("Right Meta + Middle Click/CapsLock" + type.second) << KEY_RIGHTMETA << BTN_MIDDLE << meta << true << type.first; } } void DecorationInputTest::testModifierClickUnrestrictedMove() { // this test ensures that Alt+mouse button press triggers unrestricted move // 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 QFETCH(Test::ShellSurfaceType, surfaceType); AbstractClient *c = showWindow(surfaceType); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); // move cursor on window Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); // 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(!c->isMove()); kwinApp()->platform()->pointerButtonPressed(mouseButton, timestamp++); QVERIFY(c->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); QVERIFY(c->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(mouseButton, timestamp++); QVERIFY(!c->isMove()); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } } void DecorationInputTest::testModifierScrollOpacity_data() { QTest::addColumn("modifierKey"); QTest::addColumn("modKey"); QTest::addColumn("capsLock"); QTest::addColumn("surfaceType"); const QString alt = QStringLiteral("Alt"); const QString meta = QStringLiteral("Meta"); const QVector> surfaceTypes{ {Test::ShellSurfaceType::WlShell, QByteArrayLiteral("WlShell")}, {Test::ShellSurfaceType::XdgShellV5, QByteArrayLiteral("XdgShellV5")}, }; for (const auto &type: surfaceTypes) { QTest::newRow("Left Alt" + type.second) << KEY_LEFTALT << alt << false << type.first; QTest::newRow("Right Alt" + type.second) << KEY_RIGHTALT << alt << false << type.first; QTest::newRow("Left Meta" + type.second) << KEY_LEFTMETA << meta << false << type.first; QTest::newRow("Right Meta" + type.second) << KEY_RIGHTMETA << meta << false << type.first; QTest::newRow("Left Alt/CapsLock" + type.second) << KEY_LEFTALT << alt << true << type.first; QTest::newRow("Right Alt/CapsLock" + type.second) << KEY_RIGHTALT << alt << true << type.first; QTest::newRow("Left Meta/CapsLock" + type.second) << KEY_LEFTMETA << meta << true << type.first; QTest::newRow("Right Meta/CapsLock" + type.second) << KEY_RIGHTMETA << meta << true << type.first; } } void DecorationInputTest::testModifierScrollOpacity() { // this test verifies that mod+wheel performs a window operation // 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(); QFETCH(Test::ShellSurfaceType, surfaceType); AbstractClient *c = showWindow(surfaceType); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); c->move(screens()->geometry(0).center() - QPoint(c->width()/2, c->height()/2)); // move cursor on window Cursor::setPos(QPoint(c->geometry().center().x(), c->y() + c->clientPos().y() / 2)); // set the opacity to 0.5 c->setOpacity(0.5); QCOMPARE(c->opacity(), 0.5); // 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(c->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(c->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(modifierKey, timestamp++); if (capsLock) { kwinApp()->platform()->keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); } } void DecorationInputTest::testTouchEvents_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } class EventHelper : public QObject { Q_OBJECT public: EventHelper() : QObject() {} ~EventHelper() override = default; bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched) if (event->type() == QEvent::HoverMove) { emit hoverMove(); } else if (event->type() == QEvent::HoverLeave) { emit hoverLeave(); } return false; } Q_SIGNALS: void hoverMove(); void hoverLeave(); }; void DecorationInputTest::testTouchEvents() { // this test verifies that the decoration gets a hover leave event on touch release // see BUG 386231 QFETCH(Test::ShellSurfaceType, type); AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); EventHelper helper; c->decoration()->installEventFilter(&helper); QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove); QVERIFY(hoverMoveSpy.isValid()); QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave); QVERIFY(hoverLeaveSpy.isValid()); quint32 timestamp = 1; const QPoint tapPoint(c->geometry().center().x(), c->clientPos().y() / 2); QVERIFY(!input()->touch()->decoration()); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); QVERIFY(input()->touch()->decoration()); QCOMPARE(input()->touch()->decoration()->decoration(), c->decoration()); QCOMPARE(hoverMoveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 0); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(hoverMoveSpy.count(), 1); QCOMPARE(hoverLeaveSpy.count(), 1); QCOMPARE(c->isMove(), false); // let's check that a hover motion is sent if the pointer is on deco, when touch release Cursor::setPos(tapPoint); QCOMPARE(hoverMoveSpy.count(), 2); kwinApp()->platform()->touchDown(0, tapPoint, timestamp++); QCOMPARE(hoverMoveSpy.count(), 3); QCOMPARE(hoverLeaveSpy.count(), 1); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(hoverMoveSpy.count(), 4); QCOMPARE(hoverLeaveSpy.count(), 1); } } WAYLANDTEST_MAIN(KWin::DecorationInputTest) #include "decoration_input_test.moc" diff --git a/autotests/integration/desktop_window_x11_test.cpp b/autotests/integration/desktop_window_x11_test.cpp index ca5ceeb28..9d29c1fd0 100644 --- a/autotests/integration/desktop_window_x11_test.cpp +++ b/autotests/integration/desktop_window_x11_test.cpp @@ -1,178 +1,178 @@ /******************************************************************** 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 "xcbutils.h" #include #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_x11_desktop_window-0"); class X11DesktopWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testDesktopWindow(); private: }; void X11DesktopWindowTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 X11DesktopWindowTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void X11DesktopWindowTest::cleanup() { } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void X11DesktopWindowTest::testDesktopWindow() { // this test creates a desktop window with an RGBA visual and verifies that it's only considered // as an RGB (opaque) window in KWin // 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(0, 0, 1280, 1024); // helper to find the visual auto findDepth = [&c] () -> xcb_visualid_t { // find a visual with 32 depth const xcb_setup_t *setup = xcb_get_setup(c.data()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { if (depth.data->depth != 32) { continue; } const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) { return visuals[0].visual_id; } } } return 0; }; auto visualId = findDepth(); auto colormapId = xcb_generate_id(c.data()); auto cmCookie = xcb_create_colormap_checked(c.data(), XCB_COLORMAP_ALLOC_NONE, colormapId, rootWindow(), visualId); QVERIFY(!xcb_request_check(c.data(), cmCookie)); const uint32_t values[] = {XCB_PIXMAP_NONE, defaultScreen()->black_pixel, colormapId}; auto cookie = xcb_create_window_checked(c.data(), 32, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualId, XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, values); QVERIFY(!xcb_request_check(c.data(), cookie)); 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::Desktop); xcb_map_window(c.data(), w); xcb_flush(c.data()); // verify through a geometry request that it's depth 32 Xcb::WindowGeometry geo(w); QCOMPARE(geo->depth, uint8_t(32)); // 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::Desktop); QCOMPARE(client->geometry(), windowGeometry); QVERIFY(client->isDesktop()); QCOMPARE(client->depth(), 24); QVERIFY(!client->hasAlpha()); // 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::X11DesktopWindowTest) #include "desktop_window_x11_test.moc" diff --git a/autotests/integration/dont_crash_aurorae_destroy_deco.cpp b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp index 4c712b649..811728e2a 100644 --- a/autotests/integration/dont_crash_aurorae_destroy_deco.cpp +++ b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp @@ -1,155 +1,155 @@ /******************************************************************** 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 namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_aurorae_destroy_deco-0"); class DontCrashAuroraeDestroyDecoTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testBorderlessMaximizedWindows(); }; void DontCrashAuroraeDestroyDecoTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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("library", "org.kde.kwin.aurorae"); 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 DontCrashAuroraeDestroyDecoTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DontCrashAuroraeDestroyDecoTest::testBorderlessMaximizedWindows() { // this test verifies that Aurorae doesn't crash when clicking the maximize button // with kwin config option BorderlessMaximizedWindows // see BUG 362772 // first adjust the config KConfigGroup group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->borderlessMaximizedWindows(), true); // create an xcb window xcb_connection_t *c = xcb_connect(nullptr, nullptr); QVERIFY(!xcb_connection_has_error(c)); xcb_window_t w = xcb_generate_id(c); xcb_create_window(c, XCB_COPY_FROM_PARENT, w, rootWindow(), 0, 0, 100, 200, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_map_window(c, w); xcb_flush(c); // 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->maximizeMode(), MaximizeRestore); QCOMPARE(client->noBorder(), false); // verify that the deco is Aurorae QCOMPARE(qstrcmp(client->decoration()->metaObject()->className(), "Aurorae::Decoration"), 0); // find the maximize button QQuickItem *item = client->decoration()->findChild("maximizeButton"); QVERIFY(item); const QPointF scenePoint = item->mapToScene(QPoint(0, 0)); // mark the window as ready for painting, otherwise it doesn't get input events QMetaObject::invokeMethod(client, "setReadyForPainting"); QVERIFY(client->readyForPainting()); // simulate click on maximize button QSignalSpy maximizedStateChangedSpy(client, static_cast(&AbstractClient::clientMaximizedStateChanged)); QVERIFY(maximizedStateChangedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->pointerMotion(client->geometry().topLeft() + scenePoint.toPoint(), timestamp++); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(maximizedStateChangedSpy.wait()); QCOMPARE(client->maximizeMode(), MaximizeFull); QCOMPARE(client->noBorder(), true); // and destroy the window again xcb_unmap_window(c, w); xcb_destroy_window(c, w); xcb_flush(c); xcb_disconnect(c); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::DontCrashAuroraeDestroyDecoTest) #include "dont_crash_aurorae_destroy_deco.moc" diff --git a/autotests/integration/dont_crash_empty_deco.cpp b/autotests/integration/dont_crash_empty_deco.cpp index c8e117261..d800ef13b 100644 --- a/autotests/integration/dont_crash_empty_deco.cpp +++ b/autotests/integration/dont_crash_empty_deco.cpp @@ -1,121 +1,121 @@ /******************************************************************** 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 namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_empty_decoration-0"); class DontCrashEmptyDecorationTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testBug361551(); }; void DontCrashEmptyDecorationTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // 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 DontCrashEmptyDecorationTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void DontCrashEmptyDecorationTest::testBug361551() { // this test verifies that resizing an X11 window to an invalid size does not result in crash on unmap // when the DecorationRenderer gets copied to the Deleted // there a repaint is scheduled and the resulting texture is invalid if the window size is invalid // create an xcb window xcb_connection_t *c = xcb_connect(nullptr, nullptr); QVERIFY(!xcb_connection_has_error(c)); xcb_window_t w = xcb_generate_id(c); xcb_create_window(c, XCB_COPY_FROM_PARENT, w, rootWindow(), 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); xcb_map_window(c, w); xcb_flush(c); // 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()); // let's set a stupid geometry client->setGeometry(0, 0, 0, 0); QCOMPARE(client->geometry(), QRect(0, 0, 0, 0)); // and destroy the window again xcb_unmap_window(c, w); xcb_destroy_window(c, w); xcb_flush(c); xcb_disconnect(c); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::DontCrashEmptyDecorationTest) #include "dont_crash_empty_deco.moc" diff --git a/autotests/integration/dont_crash_no_border.cpp b/autotests/integration/dont_crash_no_border.cpp index fc155ef92..e08d6b3aa 100644 --- a/autotests/integration/dont_crash_no_border.cpp +++ b/autotests/integration/dont_crash_no_border.cpp @@ -1,134 +1,134 @@ /******************************************************************** 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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/dont_crash_useractions_menu.cpp b/autotests/integration/dont_crash_useractions_menu.cpp index 486669b04..31994b69c 100644 --- a/autotests/integration/dont_crash_useractions_menu.cpp +++ b/autotests/integration/dont_crash_useractions_menu.cpp @@ -1,117 +1,117 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser 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 "useractions.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_useractions_menu-0"); class TestDontCrashUseractionsMenu : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testShowHideShowUseractionsMenu(); }; void TestDontCrashUseractionsMenu::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // force style to breeze as that's the one which triggered the crash QVERIFY(kwinApp()->setStyle(QStringLiteral("breeze"))); 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 TestDontCrashUseractionsMenu::init() { QVERIFY(Test::setupWaylandConnection()); screens()->setCurrent(0); KWin::Cursor::setPos(QPoint(1280, 512)); } void TestDontCrashUseractionsMenu::cleanup() { Test::destroyWaylandConnection(); } void TestDontCrashUseractionsMenu::testShowHideShowUseractionsMenu() { // this test creates the condition of BUG 382063 QScopedPointer surface1(Test::createSurface()); QScopedPointer shellSurface1(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface1.data())); auto client = Test::renderAndWaitForShown(surface1.data(), QSize(100, 50), Qt::blue); QVERIFY(client); workspace()->showWindowMenu(QRect(), client); auto userActionsMenu = workspace()->userActionsMenu(); QTRY_VERIFY(userActionsMenu->isShown()); QVERIFY(userActionsMenu->hasClient()); kwinApp()->platform()->keyboardKeyPressed(KEY_ESC, 0); kwinApp()->platform()->keyboardKeyReleased(KEY_ESC, 1); QTRY_VERIFY(!userActionsMenu->isShown()); QVERIFY(!userActionsMenu->hasClient()); // and show again, this triggers BUG 382063 workspace()->showWindowMenu(QRect(), client); QTRY_VERIFY(userActionsMenu->isShown()); QVERIFY(userActionsMenu->hasClient()); } WAYLANDTEST_MAIN(TestDontCrashUseractionsMenu) #include "dont_crash_useractions_menu.moc" diff --git a/autotests/integration/input_stacking_order.cpp b/autotests/integration/input_stacking_order.cpp index 7acc98e1c..e4c3d3967 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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 58bbb3687..a1fb1a6c4 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -1,677 +1,677 @@ /******************************************************************** 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(); void testOpacity(); void testMove(); void testSkipCloseAnimation_data(); void testSkipCloseAnimation(); void testModifierClickUnrestrictedMove(); void testModifierScroll(); }; 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 InternalWindowTest::init() { Cursor::setPos(QPoint(1280, 512)); 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(c->icon().name(), QStringLiteral("wayland")); QVERIFY(!c->isDecorated()); 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()); } void InternalWindowTest::testOpacity() { // this test verifies that opacity is properly synced from QWindow to ShellClient QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isInternal()); QCOMPARE(internalClient->opacity(), 0.5); QSignalSpy opacityChangedSpy(internalClient, &ShellClient::opacityChanged); QVERIFY(opacityChangedSpy.isValid()); win.setOpacity(0.75); QCOMPARE(opacityChangedSpy.count(), 1); QCOMPARE(internalClient->opacity(), 0.75); } void InternalWindowTest::testMove() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->geometry(), QRect(0, 0, 100, 100)); // normal move should be synced internalClient->move(5, 10); QCOMPARE(internalClient->geometry(), QRect(5, 10, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); // another move should also be synced internalClient->move(10, 20); QCOMPARE(internalClient->geometry(), QRect(10, 20, 100, 100)); QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100)); // now move with a Geometry update blocker { GeometryUpdatesBlocker blocker(internalClient); internalClient->move(5, 10); // not synced! QCOMPARE(win.geometry(), QRect(10, 20, 100, 100)); } // after destroying the blocker it should be synced QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); } void InternalWindowTest::testSkipCloseAnimation_data() { QTest::addColumn("initial"); QTest::newRow("set") << true; QTest::newRow("not set") << false; } void InternalWindowTest::testSkipCloseAnimation() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setOpacity(0.5); win.setGeometry(0, 0, 100, 100); QFETCH(bool, initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QCOMPARE(internalClient->skipsCloseAnimation(), initial); QSignalSpy skipCloseChangedSpy(internalClient, &Toplevel::skipCloseAnimationChanged); QVERIFY(skipCloseChangedSpy.isValid()); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial); QCOMPARE(skipCloseChangedSpy.count(), 1); QCOMPARE(internalClient->skipsCloseAnimation(), !initial); win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); QCOMPARE(skipCloseChangedSpy.count(), 2); QCOMPARE(internalClient->skipsCloseAnimation(), initial); } void InternalWindowTest::testModifierClickUnrestrictedMove() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() & ~Qt::FramelessWindowHint); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isDecorated()); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", "Alt"); group.writeEntry("CommandAll1", "Move"); group.writeEntry("CommandAll2", "Move"); group.writeEntry("CommandAll3", "Move"); group.sync(); workspace()->slotReconfigure(); QCOMPARE(options->commandAllModifier(), Qt::AltModifier); QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); // move cursor on window Cursor::setPos(internalClient->geometry().center()); // simulate modifier+click quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); QVERIFY(!internalClient->isMove()); kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(internalClient->isMove()); // release modifier should not change it kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); QVERIFY(internalClient->isMove()); // but releasing the key should end move/resize kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(!internalClient->isMove()); } void InternalWindowTest::testModifierScroll() { QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); HelperWindow win; win.setGeometry(0, 0, 100, 100); win.setFlags(win.flags() & ~Qt::FramelessWindowHint); win.show(); QVERIFY(clientAddedSpy.wait()); QCOMPARE(clientAddedSpy.count(), 1); auto internalClient = clientAddedSpy.first().first().value(); QVERIFY(internalClient); QVERIFY(internalClient->isDecorated()); KConfigGroup group = kwinApp()->config()->group("MouseBindings"); group.writeEntry("CommandAllKey", "Alt"); group.writeEntry("CommandAllWheel", "change opacity"); group.sync(); workspace()->slotReconfigure(); // move cursor on window Cursor::setPos(internalClient->geometry().center()); // set the opacity to 0.5 internalClient->setOpacity(0.5); QCOMPARE(internalClient->opacity(), 0.5); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->pointerAxisVertical(-5, timestamp++); QCOMPARE(internalClient->opacity(), 0.6); kwinApp()->platform()->pointerAxisVertical(5, timestamp++); QCOMPARE(internalClient->opacity(), 0.5); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); } } WAYLANDTEST_MAIN(KWin::InternalWindowTest) #include "internal_window.moc" diff --git a/autotests/integration/lockscreen.cpp b/autotests/integration/lockscreen.cpp index 1460c9736..30ceafffd 100644 --- a/autotests/integration/lockscreen.cpp +++ b/autotests/integration/lockscreen.cpp @@ -1,764 +1,764 @@ /******************************************************************** 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 #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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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()); action->setProperty("componentName", QStringLiteral(KWIN_NAME)); action->setObjectName("LockScreenTest::testKeyboardShortcut"); KGlobalAccel::self()->setDefaultShortcut(action.data(), QList{Qt::CTRL + Qt::META + Qt::ALT + Qt::Key_Space}); KGlobalAccel::self()->setShortcut(action.data(), QList{Qt::CTRL + Qt::META + Qt::ALT + Qt::Key_Space}, KGlobalAccel::NoAutoloading); // try to trigger the shortcut quint32 timestamp = 1; KEYPRESS(KEY_LEFTCTRL); KEYPRESS(KEY_LEFTMETA); KEYPRESS(KEY_LEFTALT); KEYPRESS(KEY_SPACE); QVERIFY(actionSpy.wait()); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QVERIFY(!actionSpy.wait()); QCOMPARE(actionSpy.count(), 1); LOCK KEYPRESS(KEY_SPACE); QVERIFY(!actionSpy.wait()); QCOMPARE(actionSpy.count(), 1); KEYRELEASE(KEY_SPACE); QVERIFY(!actionSpy.wait()); QCOMPARE(actionSpy.count(), 1); UNLOCK KEYPRESS(KEY_SPACE); QVERIFY(actionSpy.wait()); QCOMPARE(actionSpy.count(), 2); KEYRELEASE(KEY_SPACE); QVERIFY(!actionSpy.wait()); 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 530a0c6a3..2db9fece2 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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/plasma_surface_test.cpp b/autotests/integration/plasma_surface_test.cpp index 5cbc32aa9..41a966510 100644 --- a/autotests/integration/plasma_surface_test.cpp +++ b/autotests/integration/plasma_surface_test.cpp @@ -1,445 +1,446 @@ /******************************************************************** 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 testOSDPlacementManualPosition_data(); void testOSDPlacementManualPosition(); void testPanelTypeHasStrut_data(); void testPanelTypeHasStrut(); void testPanelActivate_data(); void testPanelActivate(); private: 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(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()); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QVERIFY(c != c2); QCOMPARE(c2->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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), 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)); // change size of window QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryShapeChanged); QVERIFY(geometryChangedSpy.isValid()); Test::render(surface.data(), QSize(200, 100), Qt::red); QVERIFY(geometryChangedSpy.wait()); QCOMPARE(c->geometry(), QRect(540, 616, 200, 100)); } void PlasmaSurfaceTest::testOSDPlacementManualPosition_data() { QTest::addColumn("type"); QTest::newRow("wl-shell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgv5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgv6") << Test::ShellSurfaceType::XdgShellV6; } void PlasmaSurfaceTest::testOSDPlacementManualPosition() { QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data())); QVERIFY(!plasmaSurface.isNull()); plasmaSurface->setRole(PlasmaShellSurface::Role::OnScreenDisplay); plasmaSurface->setPosition(QPoint(50, 70)); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(!shellSurface.isNull()); // now render and map the window auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isInitialPositionSet()); QCOMPARE(c->windowType(), NET::OnScreenDisplay); QVERIFY(c->isOnScreenDisplay()); QCOMPARE(c->geometry(), QRect(50, 70, 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("always visible - xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << 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("autohide - xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << 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 can cover - xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << 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; QTest::newRow("windows go below - xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << 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 87d52278a..3cc927369 100644 --- a/autotests/integration/plasmawindow_test.cpp +++ b/autotests/integration/plasmawindow_test.cpp @@ -1,367 +1,367 @@ /******************************************************************** 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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 if (!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 a9896157d..41990ed02 100644 --- a/autotests/integration/pointer_constraints_test.cpp +++ b/autotests/integration/pointer_constraints_test.cpp @@ -1,439 +1,439 @@ /******************************************************************** 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 testCloseWindowWithLockedPointer_data(); void testCloseWindowWithLockedPointer(); }; 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disables the OnScreenNotification KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("OnScreenNotification"); 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)); waylandServer()->initWorkspace(); } void TestPointerConstraints::init() { 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; QTest::newRow("XdgShellV6 - bottomLeft") << Test::ShellSurfaceType::XdgShellV6 << bottomLeft << -1 << 1; QTest::newRow("XdgShellV6 - bottomRight") << Test::ShellSurfaceType::XdgShellV6 << bottomRight << 1 << 1; QTest::newRow("XdgShellV6 - topLeft") << Test::ShellSurfaceType::XdgShellV6 << topLeft << -1 << -1; QTest::newRow("XdgShellV6 - topRight") << Test::ShellSurfaceType::XdgShellV6 << 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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(keyChangedSpy.wait()); // and just waiting should break constrain QVERIFY(unlockedSpy.wait()); QCOMPARE(keyboardLeftSpy.count(), 1); 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()); QCOMPARE(keyChangedSpy.count(), 1); // 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++); } void TestPointerConstraints::testCloseWindowWithLockedPointer_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("XdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("XdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestPointerConstraints::testCloseWindowWithLockedPointer() { // test case which verifies that the pointer gets unlocked when the window for it gets closed 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()); // close the window shellSurface.reset(); surface.reset(); // this should result in unlocked QVERIFY(unlockedSpy.wait()); QCOMPARE(input()->pointer()->isConstrained(), false); } WAYLANDTEST_MAIN(TestPointerConstraints) #include "pointer_constraints_test.moc" diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index d308dae93..48f108367 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -1,1195 +1,1196 @@ /******************************************************************** 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 "effects.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 #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 testWarpingDuringFilter(); 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(); void testPopup(); void testDecoCancelsPopup(); void testWindowUnderCursorWhileButtonPressed(); 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); } else { // might be vanilla-dmz (e.g. Arch, FreeBSD) qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); } qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); qputenv("XKB_DEFAULT_RULES", "evdev"); 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(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); 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::testWarpingDuringFilter() { // this test verifies that pointer motion is handled correctly if // the pointer gets warped during processing of input events using namespace KWayland::Client; // create pointer auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy movedSpy(pointer, &Pointer::motion); QVERIFY(movedSpy.isValid()); // warp cursor into expected geometry Cursor::setPos(10, 10); // 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); QCOMPARE(window->pos(), QPoint(0, 0)); QVERIFY(window->geometry().contains(Cursor::pos())); // is PresentWindows effect for top left screen edge loaded QVERIFY(static_cast(effects)->isEffectLoaded("presentwindows")); QVERIFY(movedSpy.isEmpty()); quint32 timestamp = 0; kwinApp()->platform()->pointerMotion(QPoint(0, 0), timestamp++); // screen edges push back QCOMPARE(Cursor::pos(), QPoint(1, 1)); QVERIFY(movedSpy.wait()); QCOMPARE(movedSpy.count(), 2); QCOMPARE(movedSpy.at(0).first().toPoint(), QPoint(0, 0)); QCOMPARE(movedSpy.at(1).first().toPoint(), QPoint(1, 1)); } 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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 1), 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_Premultiplied); 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_Premultiplied); 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()); } void PointerInputTest::testPopup() { // this test validates the basic popup behavior // a button press outside the window should dismiss the popup // first create a parent surface using namespace KWayland::Client; auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy buttonStateChangedSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonStateChangedSpy.isValid()); QSignalSpy motionSpy(pointer, &Pointer::motion); QVERIFY(motionSpy.isValid()); Cursor::setPos(800, 800); QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = Test::createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); QCOMPARE(window->hasPopupGrab(), false); // move pointer into window QVERIFY(!window->geometry().contains(QPoint(800, 800))); Cursor::setPos(window->geometry().center()); QVERIFY(enteredSpy.wait()); // click inside window to create serial quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(buttonStateChangedSpy.wait()); // now create the popup surface Surface *popupSurface = Test::createSurface(m_compositor); QVERIFY(popupSurface); ShellSurface *popupShellSurface = Test::createShellSurface(popupSurface, popupSurface); QVERIFY(popupShellSurface); QSignalSpy popupDoneSpy(popupShellSurface, &ShellSurface::popupDone); QVERIFY(popupDoneSpy.isValid()); // TODO: proper serial popupShellSurface->setTransientPopup(surface, m_seat, 0, QPoint(80, 20)); render(popupSurface); QVERIFY(clientAddedSpy.wait()); auto popupClient = clientAddedSpy.last().first().value(); QVERIFY(popupClient); QVERIFY(popupClient != window); QCOMPARE(window, workspace()->activeClient()); QCOMPARE(popupClient->transientFor(), window); QCOMPARE(popupClient->pos(), window->pos() + QPoint(80, 20)); QCOMPARE(popupClient->hasPopupGrab(), true); // let's move the pointer into the center of the window Cursor::setPos(popupClient->geometry().center()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(pointer->enteredSurface(), popupSurface); // let's move the pointer outside of the popup window // this should not really change anything, it gets a leave event Cursor::setPos(popupClient->geometry().bottomRight() + QPoint(2, 2)); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 2); QVERIFY(popupDoneSpy.isEmpty()); // now click, should trigger popupDone kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); QVERIFY(popupDoneSpy.wait()); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); } void PointerInputTest::testDecoCancelsPopup() { // this test verifies that clicking the window decoration of parent window // cancels the popup // first create a parent surface using namespace KWayland::Client; auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy buttonStateChangedSpy(pointer, &Pointer::buttonStateChanged); QVERIFY(buttonStateChangedSpy.isValid()); QSignalSpy motionSpy(pointer, &Pointer::motion); QVERIFY(motionSpy.isValid()); Cursor::setPos(800, 800); QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = Test::createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); QVERIFY(shellSurface); auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); QVERIFY(decoSpy.wait()); deco->requestMode(ServerSideDecoration::Mode::Server); QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); QCOMPARE(window->hasPopupGrab(), false); QVERIFY(window->isDecorated()); // move pointer into window QVERIFY(!window->geometry().contains(QPoint(800, 800))); Cursor::setPos(window->geometry().center()); QVERIFY(enteredSpy.wait()); // click inside window to create serial quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); QVERIFY(buttonStateChangedSpy.wait()); // now create the popup surface Surface *popupSurface = Test::createSurface(m_compositor); QVERIFY(popupSurface); ShellSurface *popupShellSurface = Test::createShellSurface(popupSurface, popupSurface); QVERIFY(popupShellSurface); QSignalSpy popupDoneSpy(popupShellSurface, &ShellSurface::popupDone); QVERIFY(popupDoneSpy.isValid()); // TODO: proper serial popupShellSurface->setTransientPopup(surface, m_seat, 0, QPoint(80, 20)); render(popupSurface); QVERIFY(clientAddedSpy.wait()); auto popupClient = clientAddedSpy.last().first().value(); QVERIFY(popupClient); QVERIFY(popupClient != window); QCOMPARE(window, workspace()->activeClient()); QCOMPARE(popupClient->transientFor(), window); QCOMPARE(popupClient->pos(), window->pos() + window->clientPos() + QPoint(80, 20)); QCOMPARE(popupClient->hasPopupGrab(), true); // let's move the pointer into the center of the deco Cursor::setPos(window->geometry().center().x(), window->y() + (window->height() - window->clientSize().height()) / 2); kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, timestamp++); QVERIFY(popupDoneSpy.wait()); kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); } void PointerInputTest::testWindowUnderCursorWhileButtonPressed() { // this test verifies that opening a window underneath the mouse cursor does not // trigger a leave event if a button is pressed // see BUG: 372876 // first create a parent surface using namespace KWayland::Client; auto pointer = m_seat->createPointer(m_seat); QVERIFY(pointer); QVERIFY(pointer->isValid()); QSignalSpy enteredSpy(pointer, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer, &Pointer::left); QVERIFY(leftSpy.isValid()); Cursor::setPos(800, 800); QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(clientAddedSpy.isValid()); Surface *surface = Test::createSurface(m_compositor); QVERIFY(surface); ShellSurface *shellSurface = Test::createShellSurface(surface, surface); QVERIFY(shellSurface); render(surface); QVERIFY(clientAddedSpy.wait()); AbstractClient *window = workspace()->activeClient(); QVERIFY(window); // move cursor over window QVERIFY(!window->geometry().contains(QPoint(800, 800))); Cursor::setPos(window->geometry().center()); QVERIFY(enteredSpy.wait()); // click inside window quint32 timestamp = 0; kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); // now create a second window as transient Surface *popupSurface = Test::createSurface(m_compositor); QVERIFY(popupSurface); ShellSurface *popupShellSurface = Test::createShellSurface(popupSurface, popupSurface); QVERIFY(popupShellSurface); popupShellSurface->setTransient(surface, QPoint(0, 0)); render(popupSurface); QVERIFY(clientAddedSpy.wait()); auto popupClient = clientAddedSpy.last().first().value(); QVERIFY(popupClient); QVERIFY(popupClient != window); QCOMPARE(window->geometry(), popupClient->geometry()); QVERIFY(!leftSpy.wait()); kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); // now that the button is no longer pressed we should get the leave event QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); QCOMPARE(enteredSpy.count(), 2); } } WAYLANDTEST_MAIN(KWin::PointerInputTest) #include "pointer_input.moc" diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp index 20a7ed9bc..f72e6ed6c 100644 --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -1,904 +1,904 @@ /******************************************************************** 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 "client.h" #include "cursor.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include "scripting/scripting.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::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(); void testQuickTilingPointerMoveXdgShell_data(); void testQuickTilingPointerMoveXdgShell(); void testQuickTilingTouchMoveXdgShell_data(); void testQuickTilingTouchMoveXdgShell(); void testX11QuickTiling_data(); void testX11QuickTiling(); void testX11QuickTilingAfterVertMaximize_data(); void testX11QuickTilingAfterVertMaximize(); void testShortcut_data(); void testShortcut(); void testScript_data(); void testScript(); 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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); qputenv("XKB_DEFAULT_RULES", "evdev"); 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(Test::AdditionalWaylandInterface::Decoration)); 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"); QTest::addColumn("expectedModeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << QRect(1280, 0, 640, 1024) << FLAG(Right); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << QRect(1280, 0, 1280, 512) << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << QRect(1920, 0, 640, 1024) << QuickTileMode(); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << QRect(1280, 512, 1280, 512) << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << QRect(1280, 0, 640, 512) << (FLAG(Right) | FLAG(Top)); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << QRect(1920, 0, 640, 512) << QuickTileMode(); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << QRect(1280, 512, 640, 512) << (FLAG(Right) | FLAG(Bottom)); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << QRect(1920, 512, 640, 512) << QuickTileMode(); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024) << QuickTileMode(); #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(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(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"); // now try to toggle again c->setQuickTileMode(mode, true); QTEST(c->quickTileMode(), "expectedModeAfterToggle"); } void QuickTilingTest::testQuickMaximizing_data() { QTest::addColumn("mode"); #define FLAG(name) QuickTileMode(QuickTileFlag::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(), QuickTileMode(QuickTileFlag::None)); 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(QuickTileFlag::Maximize, 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(), QuickTileFlag::Maximize); 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(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(), QuickTileMode(QuickTileFlag::None)); 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) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } 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(), QuickTileMode(QuickTileFlag::None)); 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) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } 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(), QuickTileMode(QuickTileFlag::None)); 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"); QTRY_COMPARE(sizeChangeSpy.count(), 1); } void QuickTilingTest::testQuickTilingPointerMoveXdgShell_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingPointerMoveXdgShell() { using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer shellSurface(Test::createXdgShellV6Surface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.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(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QVERIFY(configureRequestedSpy.wait()); QTRY_COMPARE(configureRequestedSpy.count(), 2); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); workspace()->performWindowOperation(c, Options::UnrestrictedMoveOp); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(Cursor::pos(), QPoint(49, 24)); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 3); 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"); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 4); QCOMPARE(false, configureRequestedSpy.last().first().toSize().isEmpty()); } void QuickTilingTest::testQuickTilingTouchMoveXdgShell_data() { QTest::addColumn("targetPos"); QTest::addColumn("expectedMode"); QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); } void QuickTilingTest::testQuickTilingTouchMoveXdgShell() { // test verifies that touch on decoration also allows quick tiling // see BUG: 390113 using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QScopedPointer shellSurface(Test::createXdgShellV6Surface(surface.data())); QVERIFY(!shellSurface.isNull()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); // let's render auto c = Test::renderAndWaitForShown(surface.data(), QSize(1000, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isDecorated()); const auto decoration = c->decoration(); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(c->geometry(), QRect(-decoration->borderLeft(), 0, 1000 + decoration->borderLeft() + decoration->borderRight(), 50 + decoration->borderTop() + decoration->borderBottom())); QCOMPARE(c->quickTileMode(), QuickTileMode(QuickTileFlag::None)); QCOMPARE(c->maximizeMode(), MaximizeRestore); QVERIFY(configureRequestedSpy.wait()); QTRY_COMPARE(configureRequestedSpy.count(), 2); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->touchDown(0, QPointF(c->geometry().center().x(), c->geometry().y() + decoration->borderTop() / 2), timestamp++); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(c, workspace()->getMovingClient()); QCOMPARE(configureRequestedSpy.count(), 3); QFETCH(QPoint, targetPos); kwinApp()->platform()->touchMotion(0, targetPos, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!workspace()->getMovingClient()); QCOMPARE(quickTileChangedSpy.count(), 1); QTEST(c->quickTileMode(), "expectedMode"); QVERIFY(configureRequestedSpy.wait()); QTRY_COMPARE(configureRequestedSpy.count(), 5); QCOMPARE(false, configureRequestedSpy.last().first().toSize().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void QuickTilingTest::testX11QuickTiling_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); QTest::addColumn("screen"); QTest::addColumn("modeAfterToggle"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024) << 0 << QuickTileMode(); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512) << 1 << FLAG(Top); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024) << 1 << FLAG(Left); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512) << 1 << FLAG(Bottom); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512) << 0 << QuickTileMode(); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512) << 1 << (FLAG(Left) | FLAG(Top)); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512) << 0 << QuickTileMode(); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512) << 1 << (FLAG(Left) | FLAG(Bottom)); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024) << 0 << QuickTileMode(); #undef FLAG } void QuickTilingTest::testX11QuickTiling() { 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); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); const QRect origGeo = client->geometry(); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); QCOMPARE(client->quickTileMode(), mode); QTEST(client->geometry(), "expectedGeometry"); QCOMPARE(client->geometryRestore(), origGeo); QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // quick tile to same edge again should also act like send to screen QCOMPARE(client->screen(), 0); client->setQuickTileMode(mode, true); QTEST(client->screen(), "screen"); QTEST(client->quickTileMode(), "modeAfterToggle"); QCOMPARE(client->geometryRestore(), origGeo); // 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 QuickTilingTest::testX11QuickTilingAfterVertMaximize_data() { QTest::addColumn("mode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRect(0, 0, 640, 512); QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRect(640, 0, 640, 512); QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRect(0, 512, 640, 512); QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRect(640, 512, 640, 512); QTest::newRow("maximize") << FLAG(Maximize) << QRect(0, 0, 1280, 1024); #undef FLAG } void QuickTilingTest::testX11QuickTilingAfterVertMaximize() { 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); const QRect origGeo = client->geometry(); QCOMPARE(client->maximizeMode(), MaximizeRestore); // vertically maximize the window client->maximize(client->maximizeMode() ^ MaximizeVertical); QCOMPARE(client->geometry().width(), origGeo.width()); QCOMPARE(client->height(), screens()->size(client->screen()).height()); QCOMPARE(client->geometryRestore(), origGeo); // now quick tile QSignalSpy quickTileChangedSpy(client, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QFETCH(QuickTileMode, mode); client->setQuickTileMode(mode, true); QCOMPARE(client->quickTileMode(), mode); QTEST(client->geometry(), "expectedGeometry"); QEXPECT_FAIL("", "We get two changed events", Continue); QCOMPARE(quickTileChangedSpy.count(), 1); // 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 QuickTilingTest::testShortcut_data() { QTest::addColumn("shortcut"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Window Quick Tile Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Window Quick Tile Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Window Quick Tile Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Window Quick Tile Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("Window Quick Tile Top Right") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("Window Quick Tile Top Left") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("Window Quick Tile Bottom Right") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("Window Quick Tile Bottom Left") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testShortcut() { 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(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QFETCH(QString, shortcut); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); // invoke global shortcut through dbus auto msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/component/kwin"), QStringLiteral("org.kde.kglobalaccel.Component"), QStringLiteral("invokeShortcut")); msg.setArguments(QList{shortcut}); QDBusConnection::sessionBus().asyncCall(msg); QVERIFY(quickTileChangedSpy.wait()); 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(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(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); } void QuickTilingTest::testScript_data() { QTest::addColumn("action"); QTest::addColumn("expectedMode"); QTest::addColumn("expectedGeometry"); #define FLAG(name) QuickTileMode(QuickTileFlag::name) QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512); QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); #undef FLAG } void QuickTilingTest::testScript() { 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(), QuickTileMode(QuickTileFlag::None)); QSignalSpy quickTileChangedSpy(c, &AbstractClient::quickTileModeChanged); QVERIFY(quickTileChangedSpy.isValid()); QSignalSpy geometryChangedSpy(c, &AbstractClient::geometryChanged); QVERIFY(geometryChangedSpy.isValid()); QVERIFY(Scripting::self()); QTemporaryFile tmpFile; QVERIFY(tmpFile.open()); QTextStream out(&tmpFile); QFETCH(QString, action); out << "workspace.slotWindowQuickTile" << action << "()"; out.flush(); QFETCH(QuickTileMode, expectedMode); QFETCH(QRect, expectedGeometry); const int id = Scripting::self()->loadScript(tmpFile.fileName()); QVERIFY(id != -1); QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName())); auto s = Scripting::self()->findScript(tmpFile.fileName()); QVERIFY(s); QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); QVERIFY(runningChangedSpy.isValid()); s->run(); QVERIFY(quickTileChangedSpy.wait()); QCOMPARE(quickTileChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.count(), 1); QCOMPARE(runningChangedSpy.first().first().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(), expectedMode); // but we got requested a new geometry QTRY_COMPARE(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); } } WAYLANDTEST_MAIN(KWin::QuickTilingTest) #include "quick_tiling_test.moc" diff --git a/autotests/integration/screen_changes_test.cpp b/autotests/integration/screen_changes_test.cpp index bf33d6c31..112bbe535 100644 --- a/autotests/integration/screen_changes_test.cpp +++ b/autotests/integration/screen_changes_test.cpp @@ -1,176 +1,178 @@ /******************************************************************** 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()); 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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), 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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 1), 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/screenedge_client_show_test.cpp b/autotests/integration/screenedge_client_show_test.cpp index a617e202c..06bcbac02 100644 --- a/autotests/integration/screenedge_client_show_test.cpp +++ b/autotests/integration/screenedge_client_show_test.cpp @@ -1,296 +1,296 @@ /******************************************************************** 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 namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_screenedge_client_show-0"); class ScreenEdgeClientShowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testScreenEdgeShowHideX11_data(); void testScreenEdgeShowHideX11(); void testScreenEdgeShowX11Touch_data(); void testScreenEdgeShowX11Touch(); }; void ScreenEdgeClientShowTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // set custom config which disable touch edge KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup group = config->group("TabBox"); group.writeEntry(QStringLiteral("TouchBorderActivate"), "9"); 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)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void ScreenEdgeClientShowTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); QVERIFY(waylandServer()->clients().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void ScreenEdgeClientShowTest::testScreenEdgeShowHideX11_data() { QTest::addColumn("windowGeometry"); QTest::addColumn("resizedWindowGeometry"); QTest::addColumn("location"); QTest::addColumn("triggerPos"); QTest::newRow("bottom/left") << QRect(50, 1004, 1180, 20) << QRect(150, 1004, 1000, 20) << 2u << QPoint(100, 1023); QTest::newRow("bottom/right") << QRect(1330, 1004, 1180, 20) << QRect(1410, 1004, 1000, 20) << 2u << QPoint(1400, 1023); QTest::newRow("top/left") << QRect(50, 0, 1180, 20) << QRect(150, 0, 1000, 20) << 0u << QPoint(100, 0); QTest::newRow("top/right") << QRect(1330, 0, 1180, 20) << QRect(1410, 0, 1000, 20) << 0u << QPoint(1400, 0); QTest::newRow("left") << QRect(0, 10, 20, 1000) << QRect(0, 70, 20, 800) << 3u << QPoint(0, 50); QTest::newRow("right") << QRect(2540, 10, 20, 1000) << QRect(2540, 70, 20, 800) << 1u << QPoint(2559, 60); } void ScreenEdgeClientShowTest::testScreenEdgeShowHideX11() { // this test creates a window which borders the screen and sets the screenedge show hint // that should trigger a show of the window whenever the cursor is pushed against the screen edge // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); // atom for the screenedge show hide functionality Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, 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); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(!client->isDecorated()); QCOMPARE(client->geometry(), windowGeometry); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QSignalSpy effectsWindowAdded(effects, &EffectsHandler::windowAdded); QVERIFY(effectsWindowAdded.isValid()); QVERIFY(effectsWindowAdded.wait()); // now try to hide QFETCH(quint32, location); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atom, XCB_ATOM_CARDINAL, 32, 1, &location); xcb_flush(c.data()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QSignalSpy clientHiddenSpy(client, &Client::windowHidden); QVERIFY(clientHiddenSpy.isValid()); QVERIFY(clientHiddenSpy.wait()); QVERIFY(client->isHiddenInternal()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); // now trigger the edge QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); QFETCH(QPoint, triggerPos); Cursor::setPos(triggerPos); QVERIFY(!client->isHiddenInternal()); QCOMPARE(effectsWindowShownSpy.count(), 1); // go into event loop to trigger xcb_flush QTest::qWait(1); //hide window again Cursor::setPos(QPoint(640, 512)); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atom, XCB_ATOM_CARDINAL, 32, 1, &location); xcb_flush(c.data()); QVERIFY(clientHiddenSpy.wait()); QVERIFY(client->isHiddenInternal()); QFETCH(QRect, resizedWindowGeometry); //resizewhile hidden client->setGeometry(resizedWindowGeometry); //triggerPos shouldn't be valid anymore Cursor::setPos(triggerPos); QVERIFY(client->isHiddenInternal()); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } void ScreenEdgeClientShowTest::testScreenEdgeShowX11Touch_data() { QTest::addColumn("windowGeometry"); QTest::addColumn("location"); QTest::addColumn("touchDownPos"); QTest::addColumn("targetPos"); QTest::newRow("bottom/left") << QRect(50, 1004, 1180, 20) << 2u << QPoint(100, 1023) << QPoint(100, 540); QTest::newRow("bottom/right") << QRect(1330, 1004, 1180, 20) << 2u << QPoint(1400, 1023) << QPoint(1400, 520); QTest::newRow("top/left") << QRect(50, 0, 1180, 20) << 0u << QPoint(100, 0) << QPoint(100, 350); QTest::newRow("top/right") << QRect(1330, 0, 1180, 20) << 0u << QPoint(1400, 0) << QPoint(1400, 400); QTest::newRow("left") << QRect(0, 10, 20, 1000) << 3u << QPoint(0, 50) << QPoint(400, 50); QTest::newRow("right") << QRect(2540, 10, 20, 1000) << 1u << QPoint(2559, 60) << QPoint(2200, 60); } void ScreenEdgeClientShowTest::testScreenEdgeShowX11Touch() { // this test creates a window which borders the screen and sets the screenedge show hint // that should trigger a show of the window whenever the touch screen swipe gesture is triggered // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); // atom for the screenedge show hide functionality Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, 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); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(!client->isDecorated()); QCOMPARE(client->geometry(), windowGeometry); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QSignalSpy effectsWindowAdded(effects, &EffectsHandler::windowAdded); QVERIFY(effectsWindowAdded.isValid()); QVERIFY(effectsWindowAdded.wait()); // now try to hide QFETCH(quint32, location); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atom, XCB_ATOM_CARDINAL, 32, 1, &location); xcb_flush(c.data()); QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); QVERIFY(effectsWindowHiddenSpy.isValid()); QSignalSpy clientHiddenSpy(client, &Client::windowHidden); QVERIFY(clientHiddenSpy.isValid()); QVERIFY(clientHiddenSpy.wait()); QVERIFY(client->isHiddenInternal()); QCOMPARE(effectsWindowHiddenSpy.count(), 1); // now trigger the edge QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); QVERIFY(effectsWindowShownSpy.isValid()); quint32 timestamp = 0; QFETCH(QPoint, touchDownPos); QFETCH(QPoint, targetPos); kwinApp()->platform()->touchDown(0, touchDownPos, timestamp++); kwinApp()->platform()->touchMotion(0, targetPos, timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(effectsWindowShownSpy.wait()); QVERIFY(!client->isHiddenInternal()); QCOMPARE(effectsWindowShownSpy.count(), 1); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::ScreenEdgeClientShowTest) #include "screenedge_client_show_test.moc" diff --git a/autotests/integration/shade_test.cpp b/autotests/integration/shade_test.cpp index 84c3eb3cf..787f65407 100644 --- a/autotests/integration/shade_test.cpp +++ b/autotests/integration/shade_test.cpp @@ -1,143 +1,143 @@ /******************************************************************** 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 namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_shade-0"); class ShadeTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testShadeGeometry(); }; void ShadeTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 ShadeTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); } void ShadeTest::testShadeGeometry() { // this test verifies that the geometry is properly restored after shading // see BUG: 362501 // 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->isShadeable()); QVERIFY(!client->isShade()); QVERIFY(client->isActive()); // now shade the window const QRect geoBeforeShade = client->geometry(); QVERIFY(geoBeforeShade.isValid()); QVERIFY(!geoBeforeShade.isEmpty()); workspace()->slotWindowShade(); QVERIFY(client->isShade()); QVERIFY(client->geometry() != geoBeforeShade); // and unshade again workspace()->slotWindowShade(); QVERIFY(!client->isShade()); QCOMPARE(client->geometry(), geoBeforeShade); // 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::ShadeTest) #include "shade_test.moc" diff --git a/autotests/integration/shell_client_rules_test.cpp b/autotests/integration/shell_client_rules_test.cpp index 250d97c4d..2321f7972 100644 --- a/autotests/integration/shell_client_rules_test.cpp +++ b/autotests/integration/shell_client_rules_test.cpp @@ -1,454 +1,454 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2017 Martin Flöser 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 "rules.h" #include "screens.h" #include "shell_client.h" #include "virtualdesktops.h" #include "wayland_server.h" #include "workspace.h" #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client_rules-0"); class TestShellClientRules : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testApplyInitialDesktop_data(); void testApplyInitialDesktop(); void testApplyInitialMinimize_data(); void testApplyInitialMinimize(); void testApplyInitialSkipTaskbar_data(); void testApplyInitialSkipTaskbar(); void testApplyInitialSkipPager_data(); void testApplyInitialSkipPager(); void testApplyInitialSkipSwitcher_data(); void testApplyInitialSkipSwitcher(); void testApplyInitialKeepAbove_data(); void testApplyInitialKeepAbove(); void testApplyInitialKeepBelow_data(); void testApplyInitialKeepBelow(); void testApplyInitialShortcut_data(); void testApplyInitialShortcut(); void testApplyInitialDesktopfile_data(); void testApplyInitialDesktopfile(); void testOpacityActive_data(); void testOpacityActive(); void testMatchAfterNameChange(); }; void TestShellClientRules::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 TestShellClientRules::init() { VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); screens()->setCurrent(0); } void TestShellClientRules::cleanup() { Test::destroyWaylandConnection(); } #define TEST_DATA( name ) \ void TestShellClientRules::name##_data() \ { \ QTest::addColumn("type"); \ QTest::addColumn("ruleNumber"); \ QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; \ QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; \ QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; \ QTest::newRow("wlShell|Apply") << Test::ShellSurfaceType::WlShell << 3; \ QTest::newRow("xdgShellV5|Apply") << Test::ShellSurfaceType::XdgShellV5 << 3; \ QTest::newRow("xdgShellV6|Apply") << Test::ShellSurfaceType::XdgShellV6 << 3; \ QTest::newRow("wlShell|ApplyNow") << Test::ShellSurfaceType::WlShell << 5; \ QTest::newRow("xdgShellV5|ApplyNow") << Test::ShellSurfaceType::XdgShellV5 << 5; \ QTest::newRow("xdgShellV6|ApplyNow") << Test::ShellSurfaceType::XdgShellV6 << 5; \ QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ } #define TEST_FORCE_DATA( name ) \ void TestShellClientRules::name##_data() \ { \ QTest::addColumn("type"); \ QTest::addColumn("ruleNumber"); \ QTest::newRow("wlShell|Force") << Test::ShellSurfaceType::WlShell << 2; \ QTest::newRow("xdgShellV5|Force") << Test::ShellSurfaceType::XdgShellV5 << 2; \ QTest::newRow("xdgShellV6|Force") << Test::ShellSurfaceType::XdgShellV6 << 2; \ QTest::newRow("wlShell|ForceTemporarily") << Test::ShellSurfaceType::WlShell << 6; \ QTest::newRow("xdgShellV5|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV5 << 6; \ QTest::newRow("xdgShellV6|ForceTemporarily") << Test::ShellSurfaceType::XdgShellV6 << 6; \ } TEST_DATA(testApplyInitialDesktop) void TestShellClientRules::testApplyInitialDesktop() { // ensure we have two desktops and are on first desktop VirtualDesktopManager::self()->setCount(2); VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("desktop=2\ndesktoprule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 2); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialMinimize) void TestShellClientRules::testApplyInitialMinimize() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("minimize=1\nminimizerule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), true); QCOMPARE(c->isActive(), false); c->setMinimized(false); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialSkipTaskbar) void TestShellClientRules::testApplyInitialSkipTaskbar() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("skiptaskbar=true\nskiptaskbarrule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), true); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialSkipPager) void TestShellClientRules::testApplyInitialSkipPager() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("skippager=true\nskippagerrule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), true); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialSkipSwitcher) void TestShellClientRules::testApplyInitialSkipSwitcher() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("skipswitcher=true\nskipswitcherrule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), true); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialKeepAbove) void TestShellClientRules::testApplyInitialKeepAbove() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("above=true\naboverule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), true); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialKeepBelow) void TestShellClientRules::testApplyInitialKeepBelow() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("below=true\nbelowrule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), true); QCOMPARE(c->shortcut(), QKeySequence()); } TEST_DATA(testApplyInitialShortcut) void TestShellClientRules::testApplyInitialShortcut() { // install the temporary rule QFETCH(int, ruleNumber); const QKeySequence sequence{Qt::ControlModifier + Qt::ShiftModifier + Qt::MetaModifier + Qt::AltModifier + Qt::Key_Space}; QString rule = QStringLiteral("shortcut=%1\nshortcutrule=%2").arg(sequence.toString()).arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), sequence); } TEST_DATA(testApplyInitialDesktopfile) void TestShellClientRules::testApplyInitialDesktopfile() { // install the temporary rule QFETCH(int, ruleNumber); QString rule = QStringLiteral("desktopfile=org.kde.kwin\ndesktopfilerule=%1").arg(ruleNumber); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, rule)); 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); QCOMPARE(c->desktop(), 1); QCOMPARE(c->isMinimized(), false); QCOMPARE(c->isActive(), true); QCOMPARE(c->skipTaskbar(), false); QCOMPARE(c->skipPager(), false); QCOMPARE(c->skipSwitcher(), false); QCOMPARE(c->keepAbove(), false); QCOMPARE(c->keepBelow(), false); QCOMPARE(c->shortcut(), QKeySequence()); QCOMPARE(c->desktopFileName(), QByteArrayLiteral("org.kde.kwin")); } TEST_FORCE_DATA(testOpacityActive) void TestShellClientRules::testOpacityActive() { KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("General").writeEntry("count", 1); auto group = config->group("1"); group.writeEntry("opacityactive", 90); group.writeEntry("opacityinactive", 80); QFETCH(int, ruleNumber); group.writeEntry("opacityactiverule", ruleNumber); group.writeEntry("opacityinactiverule", ruleNumber); group.sync(); RuleBook::self()->setConfig(config); workspace()->slotReconfigure(); 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(c->opacity(), 0.9); // open a second window QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(Test::createShellSurface(type, surface2.data())); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QVERIFY(c2->isActive()); QVERIFY(!c->isActive()); QCOMPARE(c2->opacity(), 0.9); QCOMPARE(c->opacity(), 0.8); workspace()->activateClient(c); QVERIFY(!c2->isActive()); QVERIFY(c->isActive()); QCOMPARE(c->opacity(), 0.9); QCOMPARE(c2->opacity(), 0.8); } void TestShellClientRules::testMatchAfterNameChange() { KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); config->group("General").writeEntry("count", 1); auto group = config->group("1"); group.writeEntry("above", true); group.writeEntry("aboverule", 2); group.writeEntry("wmclass", "org.kde.foo"); group.writeEntry("wmclasscomplete", false); group.writeEntry("wmclassmatch", 1); group.sync(); RuleBook::self()->setConfig(config); workspace()->slotReconfigure(); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgShellV6Surface(surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QCOMPARE(c->keepAbove(), false); QSignalSpy desktopFileNameSpy(c, &AbstractClient::desktopFileNameChanged); QVERIFY(desktopFileNameSpy.isValid()); shellSurface->setAppId(QByteArrayLiteral("org.kde.foo")); QVERIFY(desktopFileNameSpy.wait()); QCOMPARE(c->keepAbove(), true); } WAYLANDTEST_MAIN(TestShellClientRules) #include "shell_client_rules_test.moc" diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index b5900895d..c69291a2c 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -1,1020 +1,1020 @@ /******************************************************************** 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 #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 testWindowOutputs_data(); void testWindowOutputs(); void testMinimizeActiveWindow_data(); void testMinimizeActiveWindow(); void testFullscreen_data(); void testFullscreen(); void testUserCanSetFullscreen_data(); void testUserCanSetFullscreen(); void testUserSetFullscreenWlShell(); void testUserSetFullscreenXdgShell_data(); void testUserSetFullscreenXdgShell(); void testMaximizedToFullscreen_data(); void testMaximizedToFullscreen(); void testWindowOpensLargerThanScreen_data(); void testWindowOpensLargerThanScreen(); void testHidden_data(); void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); void testCaptionMultipleWindows(); void testUnresponsiveWindow_data(); void testUnresponsiveWindow(); void testX11WindowId_data(); void testX11WindowId(); void testAppMenu(); void testNoDecorationModeRequested_data(); void testNoDecorationModeRequested(); }; void TestShellClient::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::AppMenu)); 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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(client->icon().name(), QStringLiteral("wayland")); 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); 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::testWindowOutputs_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testWindowOutputs() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); auto size = QSize(200,200); QSignalSpy outputEnteredSpy(surface.data(), &Surface::outputEntered); QSignalSpy outputLeftSpy(surface.data(), &Surface::outputLeft); auto c = Test::renderAndWaitForShown(surface.data(), size, Qt::blue); //move to be in the first screen c->setGeometry(QRect(QPoint(100,100), size)); //we don't don't know where the compositor first placed this window, //this might fire, it might not outputEnteredSpy.wait(5); outputEnteredSpy.clear(); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0,0)); //move to overlapping both first and second screen c->setGeometry(QRect(QPoint(1250,100), size)); QVERIFY(outputEnteredSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 0); QCOMPARE(surface->outputs().count(), 2); QVERIFY(surface->outputs()[0] != surface->outputs()[1]); //move entirely into second screen c->setGeometry(QRect(QPoint(1400,100), size)); QVERIFY(outputLeftSpy.wait()); QCOMPARE(outputEnteredSpy.count(), 1); QCOMPARE(outputLeftSpy.count(), 1); QCOMPARE(surface->outputs().count(), 1); QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280,0)); } void TestShellClient::testMinimizeActiveWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << 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: case Test::ShellSurfaceType::XdgShellV6: 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: case Test::ShellSurfaceType::XdgShellV6: 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::testUserCanSetFullscreen_data() { QTest::addColumn("type"); QTest::addColumn("expected"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell << false; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5 << true; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << true; } void TestShellClient::testUserCanSetFullscreen() { 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()); QVERIFY(!c->isFullScreen()); QTEST(c->userCanSetFullScreen(), "expected"); } void TestShellClient::testUserSetFullscreenWlShell() { // wlshell cannot sync fullscreen to the client 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()); QVERIFY(!c->isFullScreen()); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(fullscreenChangedSpy.count(), 0); QVERIFY(!c->isFullScreen()); } void TestShellClient::testUserSetFullscreenXdgShell_data() { QTest::addColumn("type"); QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testUserSetFullscreenXdgShell() { QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(dynamic_cast(Test::createShellSurface(type, surface.data()))); QVERIFY(!shellSurface.isNull()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); QVERIFY(!c->isFullScreen()); // two, one for initial sync, second as it becomes active QTRY_COMPARE(configureRequestedSpy.count(), 2); QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); c->setFullScreen(true); QCOMPARE(c->isFullScreen(), true); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), screens()->size(0)); const auto states = configureRequestedSpy.first().at(1).value(); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QVERIFY(states.testFlag(KWayland::Client::XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Maximized)); QVERIFY(!states.testFlag(KWayland::Client::XdgShellSurface::State::Resizing)); QCOMPARE(fullscreenChangedSpy.count(), 1); QVERIFY(c->isFullScreen()); shellSurface->ackConfigure(configureRequestedSpy.first().at(2).value()); // unset fullscreen again c->setFullScreen(false); QCOMPARE(c->isFullScreen(), false); configureRequestedSpy.clear(); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 1); QCOMPARE(configureRequestedSpy.first().at(0).toSize(), QSize(100, 50)); QVERIFY(!configureRequestedSpy.first().at(1).value().testFlag(KWayland::Client::XdgShellSurface::State::Fullscreen)); QCOMPARE(fullscreenChangedSpy.count(), 2); QVERIFY(!c->isFullScreen()); } 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("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6 << ServerSideDecoration::Mode::Client; QTest::newRow("wlShell - deco") << Test::ShellSurfaceType::WlShell << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV5 - deco") << Test::ShellSurfaceType::XdgShellV5 << ServerSideDecoration::Mode::Server; QTest::newRow("xdgShellV6 - deco") << Test::ShellSurfaceType::XdgShellV6 << 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: case Test::ShellSurfaceType::XdgShellV6: 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: case Test::ShellSurfaceType::XdgShellV6: 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: case Test::ShellSurfaceType::XdgShellV6: 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); QEXPECT_FAIL("xdgShellV6 - 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } 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")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.foo")); QVERIFY(c->resourceName().startsWith("testShellClient")); // 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")); QCOMPARE(c->resourceClass(), QByteArrayLiteral("org.kde.bar")); QVERIFY(c->resourceName().startsWith("testShellClient")); // 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::testCaptionMultipleWindows() { QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); shellSurface->setTitle(QStringLiteral("foo")); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->caption(), QStringLiteral("foo")); QCOMPARE(c->captionNormal(), QStringLiteral("foo")); QCOMPARE(c->captionSuffix(), QString()); QScopedPointer surface2(Test::createSurface()); QScopedPointer shellSurface2(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data()))); shellSurface2->setTitle(QStringLiteral("foo")); auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); QVERIFY(c2); QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); QScopedPointer surface3(Test::createSurface()); QScopedPointer shellSurface3(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data()))); shellSurface3->setTitle(QStringLiteral("foo")); auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::blue); QVERIFY(c3); QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); QScopedPointer surface4(Test::createSurface()); QScopedPointer shellSurface4(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface4.data()))); shellSurface4->setTitle(QStringLiteral("bar")); auto c4 = Test::renderAndWaitForShown(surface4.data(), QSize(100, 50), Qt::blue); QVERIFY(c4); QCOMPARE(c4->caption(), QStringLiteral("bar")); QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); QCOMPARE(c4->captionSuffix(), QString()); QSignalSpy captionChangedSpy(c4, &ShellClient::captionChanged); QVERIFY(captionChangedSpy.isValid()); shellSurface4->setTitle(QStringLiteral("foo")); QVERIFY(captionChangedSpy.wait()); QCOMPARE(captionChangedSpy.count(), 1); QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); } void TestShellClient::testUnresponsiveWindow_data() { QTest::addColumn("shellInterface");//see env selection in qwaylandintegration.cpp QTest::addColumn("socketMode"); //wl-shell ping is not implemented //QTest::newRow("wl-shell display") << "wl-shell" << false; //QTest::newRow("wl-shell socket") << "wl-shell" << true; QTest::newRow("xdgv5 display") << "xdg-shell-v5" << false; QTest::newRow("xdgv5 socket") << "xdg-shell-v5" << true; QTest::newRow("xdgv6 display") << "xdg-shell-v6" << false; QTest::newRow("xdgv6 socket") << "xdg-shell-v6" << true; } void TestShellClient::testUnresponsiveWindow() { // this test verifies that killWindow properly terminates a process // for this an external binary is launched const QString kill = QFINDTESTDATA(QStringLiteral("kill")); QVERIFY(!kill.isEmpty()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QScopedPointer process(new QProcess); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QFETCH(QString, shellInterface); QFETCH(bool, socketMode); env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); 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(); QSignalSpy unresponsiveSpy(killClient, &AbstractClient::unresponsiveChanged); QSignalSpy killedSpy(process.data(), static_cast(&QProcess::finished)); QSignalSpy deletedSpy(killClient, &QObject::destroyed); qint64 startTime = QDateTime::currentMSecsSinceEpoch(); //wait for the process to be frozen QTest::qWait(10); //pretend the user clicked the close button killClient->closeWindow(); //client should not yet be marked unresponsive nor killed QVERIFY(!killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(unresponsiveSpy.wait()); //client should be marked unresponsive but not killed auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed1 > 900 && elapsed1 < 1200); //ping timer is 1s, but coarse timers on a test across two processes means we need a fuzzy compare QVERIFY(killClient->unresponsive()); QVERIFY(killedSpy.isEmpty()); QVERIFY(deletedSpy.wait()); if (!socketMode) { //process was killed - because we're across process this could happen in either order QVERIFY(killedSpy.count() || killedSpy.wait()); } auto elapsed2 = QDateTime::currentMSecsSinceEpoch() - startTime; QVERIFY(elapsed2 > 1800); //second ping comes in a second later } void TestShellClient::testX11WindowId_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testX11WindowId() { 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->windowId() != 0); QCOMPARE(c->window(), 0u); } void TestShellClient::testAppMenu() { //register a faux appmenu client QVERIFY (QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV6, surface.data())); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QScopedPointer menu(Test::waylandAppMenuManager()->create(surface.data())); QSignalSpy spy(c, &ShellClient::hasApplicationMenuChanged); menu->setAddress("service.name", "object/path"); spy.wait(); QCOMPARE(c->hasApplicationMenu(), true); QCOMPARE(c->applicationMenuServiceName(), QString("service.name")); QCOMPARE(c->applicationMenuObjectPath(), QString("object/path")); QVERIFY (QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); } void TestShellClient::testNoDecorationModeRequested_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void TestShellClient::testNoDecorationModeRequested() { // this test verifies that the decoration follows the default mode if no mode is explicitly requested QScopedPointer surface(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); QVERIFY(decoSpy.isValid()); if (deco->mode() != ServerSideDecoration::Mode::Server) { QVERIFY(decoSpy.wait()); } QCOMPARE(deco->mode(), ServerSideDecoration::Mode::Server); auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(c->noBorder(), false); QCOMPARE(c->isDecorated(), true); } WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/autotests/integration/struts_test.cpp b/autotests/integration/struts_test.cpp index 229437b78..b1cf00f7c 100644 --- a/autotests/integration/struts_test.cpp +++ b/autotests/integration/struts_test.cpp @@ -1,959 +1,962 @@ /******************************************************************** 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(); void testWindowMoveWithPanelBetweenScreens(); 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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)); setenv("QT_QPA_PLATFORM", "wayland", true); waylandServer()->initWorkspace(); } void StrutsTest::init() { 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::addColumn("restrictedMoveArea"); 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) << QRegion(0, 992, 1280, 32); 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) << QRegion(1280, 992, 1280, 32); 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) << QRegion(0, 0, 1280, 32); 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) << QRegion(1280, 0, 1280, 32); 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) << QRegion(0, 0, 32, 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) << QRegion(1280, 0, 32, 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) << QRegion(1248, 0, 32, 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) << QRegion(2528, 0, 32, 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) << QRegion(100, 992, 1080, 32); 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) << QRegion(1380, 992, 1080, 32); 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) << QRegion(100, 0, 1080, 32); 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) << QRegion(1380, 0, 1080, 32); 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) << QRegion(0, 100, 32, 824); 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) << QRegion(1280, 100, 32, 824); 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) << QRegion(1248, 100, 32, 824); 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) << QRegion(2528, 100, 32, 824); // 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) << QRegion(100, 992, 1080, 32).united(QRegion(1380, 984, 1080, 40)); 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) << QRegion(0, 10, 32, 390).united(QRegion(0, 450, 40, 100)); } 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)); QCOMPARE(workspace()->restrictedMoveArea(-1), QRegion()); 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"); QTEST(workspace()->restrictedMoveArea(-1), "restrictedMoveArea"); // 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()); } QCOMPARE(workspace()->restrictedMoveArea(-1), QRegion()); } 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::addColumn("restrictedMoveArea"); 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) << QRegion(); 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) << QRegion(0, 980, 1279, 44); 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) << QRegion(); 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) << QRegion(0, 0, 1279, 44); 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) << QRegion(); 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) << QRegion(0, 0, 60, 1023); QTest::newRow("right panel/no strut") << QRect(1220, 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) << QRegion(); QTest::newRow("right panel/strut") << QRect(1220, 0, 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) << QRegion(1220, 0, 60, 1023); // 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) << QRegion(); 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) << QRegion(1280, 980, 1279, 44); 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) << QRegion(); 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) << QRegion(1280, 0, 1279, 44); 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) << QRegion(); 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) << QRegion(1280, 0, 60, 1023); // 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) << QRegion(0, 980, 1280, 44); 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) << QRegion(0, 0, 1280, 44); 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) << QRegion(0, 0, 1279, 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)); QCOMPARE(workspace()->restrictedMoveArea(-1), QRegion()); // 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"); QTEST(workspace()->restrictedMoveArea(-1), "restrictedMoveArea"); // 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)); QCOMPARE(workspace()->restrictedMoveArea(-1), QRegion()); } 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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), 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", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), 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()); } void StrutsTest::testWindowMoveWithPanelBetweenScreens() { // this test verifies the condition of BUG // when moving a window with decorations in a restricted way it should pass from one screen // to the other even if there is a panel in between. // left screen must be smaller than right screen const QVector geometries{QRect(0, 282, 1366, 768), QRect(1366, 0, 1680, 1050)}; - QMetaObject::invokeMethod(kwinApp()->platform(), "outputGeometriesChanged", + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), 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 on the right screen, left edge 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(1366, 0, 24, 1050); 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 = 1050; strut.left_width = 1366+24; 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 = 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, 282, 1366, 768)); QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 1), QRect(0, 282, 1366, 768)); QCOMPARE(workspace()->clientArea(PlacementArea, 1, 1), QRect(1390, 0, 1656, 1050)); QCOMPARE(workspace()->clientArea(MaximizeArea, 1, 1), QRect(1390, 0, 1656, 1050)); QCOMPARE(workspace()->clientArea(WorkArea, 0, 1), QRect(0, 0, 3046, 1050)); QCOMPARE(workspace()->restrictedMoveArea(-1), QRegion(1366, 0, 24, 1050)); // create another window and try to move it xcb_window_t w2 = xcb_generate_id(c.data()); const QRect windowGeometry2(1500, 400, 200, 300); 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_position(&hints2, 1, windowGeometry2.x(), windowGeometry2.y()); xcb_icccm_size_hints_set_min_size(&hints2, 200, 300); 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->clientSize(), QSize(200, 300)); QCOMPARE(client2->pos(), QPoint(1500, 400)); const QRect origGeo = client2->geometry(); Cursor::setPos(origGeo.center()); workspace()->performWindowOperation(client2, Options::MoveOp); QTRY_COMPARE(workspace()->getMovingClient(), client2); QVERIFY(client2->isMove()); // move to next screen - step is 8 pixel, so 800 pixel for (int i = 0; i < 100; i++) { client2->keyPressEvent(Qt::Key_Left); QTest::qWait(50); } client2->keyPressEvent(Qt::Key_Enter); QCOMPARE(client2->isMove(), false); QVERIFY(workspace()->getMovingClient() == nullptr); QCOMPARE(client2->geometry(), QRect(origGeo.translated(-800, 0))); } } WAYLANDTEST_MAIN(KWin::StrutsTest) #include "struts_test.moc" diff --git a/autotests/integration/touch_input_test.cpp b/autotests/integration/touch_input_test.cpp index c81c1fb70..aee34c30e 100644 --- a/autotests/integration/touch_input_test.cpp +++ b/autotests/integration/touch_input_test.cpp @@ -1,260 +1,260 @@ /******************************************************************** 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 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_data(); void testMultipleTouchPoints(); void testCancel(); void testTouchMouseAction(); private: AbstractClient *showWindow(bool decorated = false); 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration)); 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(bool decorated) { 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 (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, QSize(100, 50), Qt::blue); VERIFY(c); COMPARE(workspace()->activeClient(), c); #undef VERIFY #undef COMPARE return c; } void TouchInputTest::testMultipleTouchPoints_data() { QTest::addColumn("decorated"); QTest::newRow("undecorated") << false; QTest::newRow("decorated") << true; } void TouchInputTest::testMultipleTouchPoints() { using namespace KWayland::Client; QFETCH(bool, decorated); AbstractClient *c = showWindow(decorated); QCOMPARE(c->isDecorated(), decorated); 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) + c->clientPos(), 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) + c->clientPos(), 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) + c->clientPos(), 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_placement.cpp b/autotests/integration/transient_placement.cpp index f51082c02..ac8869a2a 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(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_rules_test.cpp b/autotests/integration/window_rules_test.cpp index 20baa15eb..2bd14edc5 100644 --- a/autotests/integration/window_rules_test.cpp +++ b/autotests/integration/window_rules_test.cpp @@ -1,171 +1,171 @@ /******************************************************************** 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 "atoms.h" #include "client.h" #include "cursor.h" #include "deleted.h" #include "screenedge.h" #include "screens.h" #include "rules.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include #include namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_window_rules-0"); class WindowRuleTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testApplyInitialMaximizeVert_data(); void testApplyInitialMaximizeVert(); }; void WindowRuleTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 WindowRuleTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); QVERIFY(waylandServer()->clients().isEmpty()); } void WindowRuleTest::cleanup() { // discards old rules RuleBook::self()->load(); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void WindowRuleTest::testApplyInitialMaximizeVert_data() { QTest::addColumn("role"); QTest::newRow("lowercase") << QByteArrayLiteral("mainwindow"); QTest::newRow("CamelCase") << QByteArrayLiteral("MainWindow"); } void WindowRuleTest::testApplyInitialMaximizeVert() { // this test creates the situation of BUG 367554: creates a window and initial apply maximize vertical // the window is matched by class and role // load the rule QFile ruleFile(QFINDTESTDATA("./data/rules/maximize-vert-apply-initial")); QVERIFY(ruleFile.open(QIODevice::ReadOnly | QIODevice::Text)); QMetaObject::invokeMethod(RuleBook::self(), "temporaryRulesMessage", Q_ARG(QString, QString::fromUtf8(ruleFile.readAll()))); // create the test 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 = QRect(0, 0, 10, 20); const uint32_t values[] = { XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW }; 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, XCB_CW_EVENT_MASK, values); 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_icccm_set_wm_class(c.data(), w, 9, "kpat\0kpat"); QFETCH(QByteArray, role); xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_window_role, XCB_ATOM_STRING, 8, role.length(), role.constData()); NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); info.setWindowType(NET::Normal); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(client->isDecorated()); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QVERIFY(!client->readyForPainting()); QMetaObject::invokeMethod(client, "setReadyForPainting"); QVERIFY(client->readyForPainting()); QVERIFY(!client->surface()); QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); QVERIFY(client->surface()); QCOMPARE(client->maximizeMode(), MaximizeVertical); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::WindowRuleTest) #include "window_rules_test.moc" diff --git a/autotests/integration/window_selection_test.cpp b/autotests/integration/window_selection_test.cpp index a04cb7756..3b70a37a7 100644 --- a/autotests/integration/window_selection_test.cpp +++ b/autotests/integration/window_selection_test.cpp @@ -1,559 +1,559 @@ /******************************************************************** 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 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 testSelectOnWindowTouch(); void testCancelOnWindowPointer(); void testCancelOnWindowKeyboard(); void testSelectPointPointer(); void testSelectPointTouch(); }; 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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); qputenv("XKB_DEFAULT_RULES", "evdev"); 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(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::testSelectOnWindowTouch() { // this test verifies window selection through touch QScopedPointer touch(Test::waylandSeat()->createTouch()); QSignalSpy touchStartedSpy(touch.data(), &Touch::sequenceStarted); QVERIFY(touchStartedSpy.isValid()); QSignalSpy touchCanceledSpy(touch.data(), &Touch::sequenceCanceled); QVERIFY(touchCanceledSpy.isValid()); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createShellSurface(surface.data())); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); 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); // simulate touch down quint32 timestamp = 0; kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); QVERIFY(!selectedWindow); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(selectedWindow, client); // with movement selectedWindow = nullptr; kwinApp()->platform()->startInteractiveWindowSelection(callback); kwinApp()->platform()->touchDown(0, client->geometry().bottomRight() + QPoint(20, 20), timestamp++); QVERIFY(!selectedWindow); kwinApp()->platform()->touchMotion(0, client->geometry().bottomRight() - QPoint(1, 1), timestamp++); QVERIFY(!selectedWindow); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(selectedWindow, client); QCOMPARE(input()->isSelectingWindow(), false); // it cancels active touch sequence on the window kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); QVERIFY(touchStartedSpy.wait()); selectedWindow = nullptr; kwinApp()->platform()->startInteractiveWindowSelection(callback); QCOMPARE(input()->isSelectingWindow(), true); QVERIFY(touchCanceledSpy.wait()); QVERIFY(!selectedWindow); // this touch up does not yet select the window, it was started prior to the selection kwinApp()->platform()->touchUp(0, timestamp++); QVERIFY(!selectedWindow); kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(selectedWindow, client); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(touchStartedSpy.count(), 1); QCOMPARE(touchCanceledSpy.count(), 1); } 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); } void TestWindowSelection::testSelectPointTouch() { // this test verifies point selection through touch works 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()); // let's create multiple touch points quint32 timestamp = 0; kwinApp()->platform()->touchDown(0, QPointF(0, 1), timestamp++); QCOMPARE(input()->isSelectingWindow(), true); kwinApp()->platform()->touchDown(1, QPointF(10, 20), timestamp++); QCOMPARE(input()->isSelectingWindow(), true); kwinApp()->platform()->touchDown(2, QPointF(30, 40), timestamp++); QCOMPARE(input()->isSelectingWindow(), true); // let's move our points kwinApp()->platform()->touchMotion(0, QPointF(5, 10), timestamp++); kwinApp()->platform()->touchMotion(2, QPointF(20, 25), timestamp++); kwinApp()->platform()->touchMotion(1, QPointF(25, 35), timestamp++); QCOMPARE(input()->isSelectingWindow(), true); kwinApp()->platform()->touchUp(0, timestamp++); QCOMPARE(input()->isSelectingWindow(), true); kwinApp()->platform()->touchUp(2, timestamp++); QCOMPARE(input()->isSelectingWindow(), true); kwinApp()->platform()->touchUp(1, timestamp++); QCOMPARE(input()->isSelectingWindow(), false); QCOMPARE(point, QPoint(25, 35)); } WAYLANDTEST_MAIN(TestWindowSelection) #include "window_selection_test.moc" diff --git a/autotests/integration/xclipboardsync_test.cpp b/autotests/integration/xclipboardsync_test.cpp index 212eee217..ee9a5e675 100644 --- a/autotests/integration/xclipboardsync_test.cpp +++ b/autotests/integration/xclipboardsync_test.cpp @@ -1,183 +1,183 @@ /******************************************************************** 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 "shell_client.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_xclipboard_sync-0"); class XClipboardSyncTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testSync_data(); void testSync(); private: QProcess *m_copyProcess = nullptr; QProcess *m_pasteProcess = nullptr; }; void XClipboardSyncTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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(); // wait till the xclipboard sync data device is created while (waylandServer()->xclipboardSyncDataDevice().isNull()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } QVERIFY(!waylandServer()->xclipboardSyncDataDevice().isNull()); } void XClipboardSyncTest::cleanup() { if (m_copyProcess) { m_copyProcess->terminate(); QVERIFY(m_copyProcess->waitForFinished()); m_copyProcess = nullptr; } if (m_pasteProcess) { m_pasteProcess->terminate(); QVERIFY(m_pasteProcess->waitForFinished()); m_pasteProcess = nullptr; } } void XClipboardSyncTest::testSync_data() { QTest::addColumn("copyPlatform"); QTest::addColumn("pastePlatform"); QTest::newRow("x11-wayland") << QStringLiteral("xcb") << QStringLiteral("wayland"); QTest::newRow("wayland-x11") << QStringLiteral("wayland") << QStringLiteral("xcb"); } void XClipboardSyncTest::testSync() { // this test verifies the syncing of X11 to Wayland clipboard const QString copy = QFINDTESTDATA(QStringLiteral("copy")); QVERIFY(!copy.isEmpty()); const QString paste = QFINDTESTDATA(QStringLiteral("paste")); QVERIFY(!paste.isEmpty()); QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded); QVERIFY(clientAddedSpy.isValid()); QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); QVERIFY(shellClientAddedSpy.isValid()); QSignalSpy clipboardChangedSpy(waylandServer()->xclipboardSyncDataDevice().data(), &KWayland::Server::DataDeviceInterface::selectionChanged); QVERIFY(clipboardChangedSpy.isValid()); QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); QFETCH(QString, copyPlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), copyPlatform); environment.insert(QStringLiteral("WAYLAND_DISPLAY"), s_socketName); m_copyProcess = new QProcess(); m_copyProcess->setProcessEnvironment(environment); m_copyProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_copyProcess->setProgram(copy); m_copyProcess->start(); QVERIFY(m_copyProcess->waitForStarted()); AbstractClient *copyClient = nullptr; if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); copyClient = clientAddedSpy.first().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); copyClient = shellClientAddedSpy.first().first().value(); } QVERIFY(copyClient); if (workspace()->activeClient() != copyClient) { workspace()->activateClient(copyClient); } QCOMPARE(workspace()->activeClient(), copyClient); if (copyPlatform == QLatin1String("xcb")) { QVERIFY(clipboardChangedSpy.isEmpty()); QVERIFY(clipboardChangedSpy.wait()); } else { // TODO: it would be better to be able to connect to a signal, instead of waiting // the idea is to make sure that the clipboard is updated, thus we need to give it // enough time before starting the paste process which creates another window QTest::qWait(250); } // start the paste process m_pasteProcess = new QProcess(); QSignalSpy finishedSpy(m_pasteProcess, static_cast(&QProcess::finished)); QVERIFY(finishedSpy.isValid()); QFETCH(QString, pastePlatform); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), pastePlatform); m_pasteProcess->setProcessEnvironment(environment); m_pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels); m_pasteProcess->setProgram(paste); m_pasteProcess->start(); QVERIFY(m_pasteProcess->waitForStarted()); AbstractClient *pasteClient = nullptr; if (pastePlatform == QLatin1String("xcb")) { QVERIFY(clientAddedSpy.wait()); pasteClient = clientAddedSpy.last().first().value(); } else { QVERIFY(shellClientAddedSpy.wait()); pasteClient = shellClientAddedSpy.last().first().value(); } QCOMPARE(clientAddedSpy.count(), 1); QCOMPARE(shellClientAddedSpy.count(), 1); QVERIFY(pasteClient); if (workspace()->activeClient() != pasteClient) { QSignalSpy clientActivatedSpy(workspace(), &Workspace::clientActivated); QVERIFY(clientActivatedSpy.isValid()); workspace()->activateClient(pasteClient); QVERIFY(clientActivatedSpy.wait()); } QTRY_COMPARE(workspace()->activeClient(), pasteClient); QVERIFY(finishedSpy.wait()); QCOMPARE(finishedSpy.first().first().toInt(), 0); delete m_pasteProcess; m_pasteProcess = nullptr; } WAYLANDTEST_MAIN(XClipboardSyncTest) #include "xclipboardsync_test.moc" diff --git a/autotests/integration/xwayland_input_test.cpp b/autotests/integration/xwayland_input_test.cpp index 7de3cbd87..a88bc3a15 100644 --- a/autotests/integration/xwayland_input_test.cpp +++ b/autotests/integration/xwayland_input_test.cpp @@ -1,212 +1,212 @@ /******************************************************************** 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 namespace KWin { static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_input-0"); class XWaylandInputTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void testPointerEnterLeave(); }; void XWaylandInputTest::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)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", 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 XWaylandInputTest::init() { screens()->setCurrent(0); Cursor::setPos(QPoint(640, 512)); QVERIFY(waylandServer()->clients().isEmpty()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; class X11EventReaderHelper : public QObject { Q_OBJECT public: X11EventReaderHelper(xcb_connection_t *c); Q_SIGNALS: void entered(); void left(); private: void processXcbEvents(); xcb_connection_t *m_connection; QSocketNotifier *m_notifier; }; X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) : QObject() , m_connection(c) , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) { connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); } void X11EventReaderHelper::processXcbEvents() { while (auto event = xcb_poll_for_event(m_connection)) { const uint8_t eventType = event->response_type & ~0x80; switch (eventType) { case XCB_ENTER_NOTIFY: emit entered(); break; case XCB_LEAVE_NOTIFY: emit left(); break; } free(event); } xcb_flush(m_connection); } void XWaylandInputTest::testPointerEnterLeave() { // this test simulates a pointer enter and pointer leave on an X11 window // create the test window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); if (xcb_get_setup(c.data())->release_number < 11800000) { QSKIP("XWayland 1.18 required"); } X11EventReaderHelper eventReader(c.data()); QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); QVERIFY(leftSpy.isValid()); // atom for the screenedge show hide functionality Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.data()); xcb_window_t w = xcb_generate_id(c.data()); const QRect windowGeometry = QRect(0, 0, 10, 20); const uint32_t values[] = { XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW }; 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, XCB_CW_EVENT_MASK, values); 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::Normal); xcb_map_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.last().first().value(); QVERIFY(client); QVERIFY(client->isDecorated()); QVERIFY(!client->hasStrut()); QVERIFY(!client->isHiddenInternal()); QVERIFY(!client->readyForPainting()); QMetaObject::invokeMethod(client, "setReadyForPainting"); QVERIFY(client->readyForPainting()); QVERIFY(!client->surface()); QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); QVERIFY(client->surface()); // move pointer into the window, should trigger an enter QVERIFY(!client->geometry().contains(Cursor::pos())); QVERIFY(enteredSpy.isEmpty()); Cursor::setPos(client->geometry().center()); QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface()); QVERIFY(waylandServer()->seat()->focusedPointer()); QVERIFY(enteredSpy.wait()); // move out of window Cursor::setPos(client->geometry().bottomRight() + QPoint(10, 10)); QVERIFY(leftSpy.wait()); // destroy window again QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); xcb_unmap_window(c.data(), w); xcb_destroy_window(c.data(), w); xcb_flush(c.data()); QVERIFY(windowClosedSpy.wait()); } } WAYLANDTEST_MAIN(KWin::XWaylandInputTest) #include "xwayland_input_test.moc" diff --git a/plugins/platforms/virtual/CMakeLists.txt b/plugins/platforms/virtual/CMakeLists.txt index 673bfe314..4f41b353e 100644 --- a/plugins/platforms/virtual/CMakeLists.txt +++ b/plugins/platforms/virtual/CMakeLists.txt @@ -1,24 +1,25 @@ set(VIRTUAL_SOURCES egl_gbm_backend.cpp virtual_backend.cpp + virtual_output.cpp scene_qpainter_virtual_backend.cpp screens_virtual.cpp ) include_directories(${CMAKE_SOURCE_DIR}/platformsupport/scenes/opengl) include(ECMQtDeclareLoggingCategory) ecm_qt_declare_logging_category(VIRTUAL_SOURCES HEADER logging.h IDENTIFIER KWIN_VIRTUAL CATEGORY_NAME kwin_platform_virtual DEFAULT_SEVERITY Critical) add_library(KWinWaylandVirtualBackend MODULE ${VIRTUAL_SOURCES}) target_link_libraries(KWinWaylandVirtualBackend kwin SceneQPainterBackend SceneOpenGLBackend) if(HAVE_GBM) target_link_libraries(KWinWaylandVirtualBackend gbm::gbm) endif() install( TARGETS KWinWaylandVirtualBackend DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kwin.waylandbackends/ ) diff --git a/plugins/platforms/virtual/screens_virtual.cpp b/plugins/platforms/virtual/screens_virtual.cpp index 7cd2fdfb9..49b3836c6 100644 --- a/plugins/platforms/virtual/screens_virtual.cpp +++ b/plugins/platforms/virtual/screens_virtual.cpp @@ -1,98 +1,94 @@ /******************************************************************** 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 "screens_virtual.h" #include "virtual_backend.h" +#include "virtual_output.h" namespace KWin { VirtualScreens::VirtualScreens(VirtualBackend *backend, QObject *parent) : Screens(parent) , m_backend(backend) { } VirtualScreens::~VirtualScreens() = default; void VirtualScreens::init() { updateCount(); KWin::Screens::init(); - connect(m_backend, &VirtualBackend::sizeChanged, - this, &VirtualScreens::startChangedTimer); - connect(m_backend, &VirtualBackend::outputGeometriesChanged, this, - [this] (const QVector &geometries) { - const int oldCount = m_geometries.count(); - m_geometries = geometries; - if (oldCount != m_geometries.count()) { - setCount(m_geometries.count()); + + connect(m_backend, &VirtualBackend::virtualOutputsSet, this, + [this] (bool countChanged) { + if (countChanged) { + setCount(m_backend->outputCount()); } else { emit changed(); } } ); + emit changed(); } QRect VirtualScreens::geometry(int screen) const { - if (screen >= m_geometries.count()) { + const auto outputs = m_backend->outputs(); + if (screen >= outputs.size()) { return QRect(); } - return m_geometries.at(screen); + return outputs.at(screen).geometry(); } QSize VirtualScreens::size(int screen) const { return geometry(screen).size(); } void VirtualScreens::updateCount() { - m_geometries.clear(); - const QSize size = m_backend->size(); - for (int i = 0; i < m_backend->outputCount(); ++i) { - m_geometries.append(QRect(size.width() * i, 0, size.width(), size.height())); - } setCount(m_backend->outputCount()); } int VirtualScreens::number(const QPoint &pos) const { int bestScreen = 0; int minDistance = INT_MAX; - for (int i = 0; i < m_geometries.count(); ++i) { - const QRect &geo = m_geometries.at(i); + const auto outputs = m_backend->outputs(); + for (int i = 0; i < outputs.size(); ++i) { + const QRect &geo = outputs.at(i).geometry(); if (geo.contains(pos)) { return i; } int distance = QPoint(geo.topLeft() - pos).manhattanLength(); distance = qMin(distance, QPoint(geo.topRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomRight() - pos).manhattanLength()); distance = qMin(distance, QPoint(geo.bottomLeft() - pos).manhattanLength()); if (distance < minDistance) { minDistance = distance; bestScreen = i; } } return bestScreen; } } diff --git a/plugins/platforms/virtual/screens_virtual.h b/plugins/platforms/virtual/screens_virtual.h index c5fdbb68f..d7962257f 100644 --- a/plugins/platforms/virtual/screens_virtual.h +++ b/plugins/platforms/virtual/screens_virtual.h @@ -1,50 +1,49 @@ /******************************************************************** 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_SCREENS_VIRTUAL_H #define KWIN_SCREENS_VIRTUAL_H #include "screens.h" #include namespace KWin { class VirtualBackend; class VirtualScreens : public Screens { Q_OBJECT public: VirtualScreens(VirtualBackend *backend, QObject *parent = nullptr); virtual ~VirtualScreens(); void init() override; QRect geometry(int screen) const override; int number(const QPoint &pos) const override; QSize size(int screen) const override; void updateCount() override; private: void createOutputs(); VirtualBackend *m_backend; - QVector m_geometries; }; } #endif diff --git a/plugins/platforms/virtual/virtual_backend.cpp b/plugins/platforms/virtual/virtual_backend.cpp index d41cd6988..1afc3d68a 100644 --- a/plugins/platforms/virtual/virtual_backend.cpp +++ b/plugins/platforms/virtual/virtual_backend.cpp @@ -1,112 +1,146 @@ /******************************************************************** 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 "virtual_backend.h" #include "scene_qpainter_virtual_backend.h" #include "screens_virtual.h" #include "wayland_server.h" #include "egl_gbm_backend.h" // Qt #include // KWayland #include // system #include #include #include #if HAVE_GBM #include #endif #include namespace KWin { VirtualBackend::VirtualBackend(QObject *parent) : Platform(parent) { if (qEnvironmentVariableIsSet("KWIN_WAYLAND_VIRTUAL_SCREENSHOTS")) { m_screenshotDir.reset(new QTemporaryDir); if (!m_screenshotDir->isValid()) { m_screenshotDir.reset(); } if (!m_screenshotDir.isNull()) { qDebug() << "Screenshots saved to: " << m_screenshotDir->path(); } } setSupportsPointerWarping(true); setSupportsGammaControl(true); } VirtualBackend::~VirtualBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_drmFd != -1) { close(m_drmFd); } } void VirtualBackend::init() { + /* + * Some tests currently expect one output present at start, + * others set them explicitly. + * + * TODO: rewrite all tests to explicitly set the outputs. + */ + if (!m_outputs.size()) { + auto dummyOutput = VirtualOutput(this); + dummyOutput.m_geo = QRect(QPoint(0, 0), initialWindowSize()); + m_outputs = { dummyOutput }; + } + + setSoftWareCursor(true); - m_size = initialWindowSize(); setReady(true); waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasKeyboard(true); waylandServer()->seat()->setHasTouch(true); + emit screensQueried(); } QString VirtualBackend::screenshotDirPath() const { if (m_screenshotDir.isNull()) { return QString(); } return m_screenshotDir->path(); } Screens *VirtualBackend::createScreens(QObject *parent) { return new VirtualScreens(this, parent); } QPainterBackend *VirtualBackend::createQPainterBackend() { return new VirtualQPainterBackend(this); } OpenGLBackend *VirtualBackend::createOpenGLBackend() { return new EglGbmBackend(this); } +void VirtualBackend::setVirtualOutputs(int count, QVector geometries) +{ + Q_ASSERT(geometries.size() == 0 || geometries.size() == count); + + bool countChanged = m_outputs.size() != count; + m_outputs.resize(count); + + int sumWidth = 0; + for (int i = 0; i < count; i++) { + VirtualOutput& o = m_outputs[i]; + if (geometries.size()) { + o.m_geo = geometries.at(i); + } else if (!o.m_geo.isValid()) { + o.m_geo = QRect(QPoint(sumWidth, 0), initialWindowSize()); + sumWidth += o.m_geo.width(); + } + } + + emit virtualOutputsSet(countChanged); +} + int VirtualBackend::gammaRampSize(int screen) const { - return m_gammaSizes[screen]; + return m_outputs[screen].m_gammaSize; } bool VirtualBackend::setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) { Q_UNUSED(gamma); - return m_gammaResults[screen]; + return m_outputs[screen].m_gammaResult; } } diff --git a/plugins/platforms/virtual/virtual_backend.h b/plugins/platforms/virtual/virtual_backend.h index 6c2eae152..c0baa698b 100644 --- a/plugins/platforms/virtual/virtual_backend.h +++ b/plugins/platforms/virtual/virtual_backend.h @@ -1,119 +1,109 @@ /******************************************************************** 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_VIRTUAL_BACKEND_H #define KWIN_VIRTUAL_BACKEND_H #include "platform.h" +#include "virtual_output.h" #include #include -#include +#include class QTemporaryDir; struct gbm_device; namespace KWin { -namespace ColorCorrect { -class Manager; -struct GammaRamp; -} - +struct ColorCorrect::GammaRamp; class KWIN_EXPORT VirtualBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "virtual.json") - Q_PROPERTY(QSize size READ size NOTIFY sizeChanged) + public: VirtualBackend(QObject *parent = nullptr); virtual ~VirtualBackend(); void init() override; - QSize size() const { - return m_size; - } int outputCount() const { - return m_outputCount; + return m_outputs.size(); + } + const QVector outputs() const { + return m_outputs; } qreal outputScale() const { return m_outputScale; } bool saveFrames() const { return !m_screenshotDir.isNull(); } QString screenshotDirPath() const; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend* createQPainterBackend() override; OpenGLBackend *createOpenGLBackend() override; - Q_INVOKABLE void setOutputCount(int count) { - m_outputCount = count; - m_gammaSizes = QVector(count, 200); - m_gammaResults = QVector(count, true); - } + Q_INVOKABLE void setVirtualOutputs(int count, QVector geometries = QVector()); Q_INVOKABLE void setOutputScale(qreal scale) { m_outputScale = scale; } int drmFd() const { return m_drmFd; } void setDrmFd(int fd) { m_drmFd = fd; } gbm_device *gbmDevice() const { return m_gbmDevice; } void setGbmDevice(gbm_device *device) { m_gbmDevice = device; } virtual int gammaRampSize(int screen) const override; virtual bool setGammaRamp(int screen, ColorCorrect::GammaRamp &gamma) override; QVector supportedCompositors() const override { return QVector{OpenGLCompositing, QPainterCompositing}; } Q_SIGNALS: - void sizeChanged(); - void outputGeometriesChanged(const QVector &geometries); + void virtualOutputsSet(bool countChanged); private: - QSize m_size; - int m_outputCount = 1; + QVector m_outputs; + qreal m_outputScale = 1; + QScopedPointer m_screenshotDir; int m_drmFd = -1; gbm_device *m_gbmDevice = nullptr; - - QVector m_gammaSizes = QVector(1, 200); - QVector m_gammaResults = QVector(1, true); }; } #endif diff --git a/plugins/platforms/virtual/screens_virtual.h b/plugins/platforms/virtual/virtual_output.cpp similarity index 56% copy from plugins/platforms/virtual/screens_virtual.h copy to plugins/platforms/virtual/virtual_output.cpp index c5fdbb68f..e402f8ca4 100644 --- a/plugins/platforms/virtual/screens_virtual.h +++ b/plugins/platforms/virtual/virtual_output.cpp @@ -1,50 +1,42 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2018 Roman Gilg 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_SCREENS_VIRTUAL_H -#define KWIN_SCREENS_VIRTUAL_H -#include "screens.h" -#include +#include "virtual_output.h" namespace KWin { -class VirtualBackend; -class VirtualScreens : public Screens +VirtualOutput::VirtualOutput(QObject *parent) + : QObject(parent) { - Q_OBJECT -public: - VirtualScreens(VirtualBackend *backend, QObject *parent = nullptr); - virtual ~VirtualScreens(); - void init() override; - QRect geometry(int screen) const override; - int number(const QPoint &pos) const override; - QSize size(int screen) const override; - void updateCount() override; - -private: - void createOutputs(); - VirtualBackend *m_backend; - QVector m_geometries; -}; +} +VirtualOutput::VirtualOutput(const VirtualOutput &o) + : m_geo(o.m_geo), + m_outputScale(o.m_outputScale), + m_gammaSize(o.m_gammaSize), + m_gammaResult(o.m_gammaResult) +{ } -#endif +VirtualOutput::~VirtualOutput() +{ +} +} diff --git a/plugins/platforms/virtual/screens_virtual.h b/plugins/platforms/virtual/virtual_output.h similarity index 60% copy from plugins/platforms/virtual/screens_virtual.h copy to plugins/platforms/virtual/virtual_output.h index c5fdbb68f..762ed1eb3 100644 --- a/plugins/platforms/virtual/screens_virtual.h +++ b/plugins/platforms/virtual/virtual_output.h @@ -1,50 +1,55 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. -Copyright (C) 2015 Martin Gräßlin +Copyright (C) 2018 Roman Gilg 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_SCREENS_VIRTUAL_H -#define KWIN_SCREENS_VIRTUAL_H -#include "screens.h" -#include +#ifndef KWIN_VIRTUAL_OUTPUT_H +#define KWIN_VIRTUAL_OUTPUT_H + +#include +#include namespace KWin { class VirtualBackend; -class VirtualScreens : public Screens +class VirtualOutput : public QObject { Q_OBJECT + public: - VirtualScreens(VirtualBackend *backend, QObject *parent = nullptr); - virtual ~VirtualScreens(); - void init() override; - QRect geometry(int screen) const override; - int number(const QPoint &pos) const override; - QSize size(int screen) const override; - void updateCount() override; + VirtualOutput(QObject *parent = nullptr); + VirtualOutput(const VirtualOutput &o); + virtual ~VirtualOutput(); + + QRect geometry() const { + return m_geo; + } private: - void createOutputs(); - VirtualBackend *m_backend; - QVector m_geometries; + friend class VirtualBackend; + + QRect m_geo; + qreal m_outputScale = 1; + + int m_gammaSize = 200; + bool m_gammaResult = true; }; } #endif -