/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2017 Martin Flöser SPDX-FileCopyrightText: 2019 Vlad Zahorodnii SPDX-License-Identifier: GPL-2.0-or-later */ #include "kwin_wayland_test.h" #include "abstract_client.h" #include "abstract_output.h" #include "cursor.h" #include "platform.h" #include "rules.h" #include "virtualdesktops.h" #include "wayland_server.h" #include "waylandoutputconfig.h" #include "workspace.h" #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_xdgshellclient_rules-0"); class TestXdgShellClientRules : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testPositionDontAffect(); void testPositionApply(); void testPositionRemember(); void testPositionForce(); void testPositionApplyNow(); void testPositionForceTemporarily(); void testSizeDontAffect(); void testSizeApply(); void testSizeRemember(); void testSizeForce(); void testSizeApplyNow(); void testSizeForceTemporarily(); void testMaximizeDontAffect(); void testMaximizeApply(); void testMaximizeRemember(); void testMaximizeForce(); void testMaximizeApplyNow(); void testMaximizeForceTemporarily(); void testDesktopDontAffect(); void testDesktopApply(); void testDesktopRemember(); void testDesktopForce(); void testDesktopApplyNow(); void testDesktopForceTemporarily(); void testMinimizeDontAffect(); void testMinimizeApply(); void testMinimizeRemember(); void testMinimizeForce(); void testMinimizeApplyNow(); void testMinimizeForceTemporarily(); void testSkipTaskbarDontAffect(); void testSkipTaskbarApply(); void testSkipTaskbarRemember(); void testSkipTaskbarForce(); void testSkipTaskbarApplyNow(); void testSkipTaskbarForceTemporarily(); void testSkipPagerDontAffect(); void testSkipPagerApply(); void testSkipPagerRemember(); void testSkipPagerForce(); void testSkipPagerApplyNow(); void testSkipPagerForceTemporarily(); void testSkipSwitcherDontAffect(); void testSkipSwitcherApply(); void testSkipSwitcherRemember(); void testSkipSwitcherForce(); void testSkipSwitcherApplyNow(); void testSkipSwitcherForceTemporarily(); void testKeepAboveDontAffect(); void testKeepAboveApply(); void testKeepAboveRemember(); void testKeepAboveForce(); void testKeepAboveApplyNow(); void testKeepAboveForceTemporarily(); void testKeepBelowDontAffect(); void testKeepBelowApply(); void testKeepBelowRemember(); void testKeepBelowForce(); void testKeepBelowApplyNow(); void testKeepBelowForceTemporarily(); void testShortcutDontAffect(); void testShortcutApply(); void testShortcutRemember(); void testShortcutForce(); void testShortcutApplyNow(); void testShortcutForceTemporarily(); void testDesktopFileDontAffect(); void testDesktopFileApply(); void testDesktopFileRemember(); void testDesktopFileForce(); void testDesktopFileApplyNow(); void testDesktopFileForceTemporarily(); void testActiveOpacityDontAffect(); void testActiveOpacityForce(); void testActiveOpacityForceTemporarily(); void testInactiveOpacityDontAffect(); void testInactiveOpacityForce(); void testInactiveOpacityForceTemporarily(); void testNoBorderDontAffect(); void testNoBorderApply(); void testNoBorderRemember(); void testNoBorderForce(); void testNoBorderApplyNow(); void testNoBorderForceTemporarily(); void testScreenDontAffect(); void testScreenApply(); void testScreenRemember(); void testScreenForce(); void testScreenApplyNow(); void testScreenForceTemporarily(); void testMatchAfterNameChange(); private: template void setWindowRule(const QString &property, const T &value, int policy); private: KSharedConfig::Ptr m_config; }; void TestXdgShellClientRules::initTestCase() { qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(applicationStartedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName)); QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); kwinApp()->start(); QVERIFY(applicationStartedSpy.wait()); const auto outputs = kwinApp()->platform()->enabledOutputs(); QCOMPARE(outputs.count(), 2); QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); Test::initWaylandWorkspace(); m_config = KSharedConfig::openConfig(QStringLiteral("kwinrulesrc"), KConfig::SimpleConfig); RuleBook::self()->setConfig(m_config); } void TestXdgShellClientRules::init() { VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1)); workspace()->setActiveOutput(QPoint(640, 512)); } void TestXdgShellClientRules::cleanup() { Test::destroyWaylandConnection(); // Wipe the window rule config clean. for (const QString &group : m_config->groupList()) { m_config->deleteGroup(group); } workspace()->slotReconfigure(); // Restore virtual desktops to the initial state. VirtualDesktopManager::self()->setCount(1); QCOMPARE(VirtualDesktopManager::self()->count(), 1u); } std::tuple createWindow(const QString &appId, Test::XdgToplevelDecorationV1::mode decorationMode = Test::XdgToplevelDecorationV1::mode_client_side) { // Create an xdg surface. KWayland::Client::Surface *surface = Test::createSurface(); Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface, Test::CreationSetup::CreateOnly, surface); Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface); shellSurface->set_app_id(appId); decoration->set_mode(decorationMode); // Wait for the initial configure event. QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); surface->commit(KWayland::Client::Surface::CommitFlag::None); configureRequestedSpy.wait(); // Draw content of the surface. shellSurface->xdgSurface()->ack_configure(configureRequestedSpy.last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); return {client, surface, shellSurface}; } template void TestXdgShellClientRules::setWindowRule(const QString &property, const T &value, int policy) { // Initialize RuleBook with the test rule. m_config->group("General").writeEntry("count", 1); KConfigGroup group = m_config->group("1"); group.writeEntry(property, value); group.writeEntry(QStringLiteral("%1rule").arg(property), policy); group.writeEntry("wmclass", "org.kde.foo"); group.writeEntry("wmclasscomplete", false); group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); group.sync(); workspace()->slotReconfigure(); } void TestXdgShellClientRules::testPositionDontAffect() { setWindowRule("position", QPoint(42, 42), int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The position of the client should not be affected by the rule. The default // placement policy will put the client in the top-left corner of the screen. QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(0, 0)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testPositionApply() { setWindowRule("position", QPoint(42, 42), int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The client should be moved to the position specified by the rule. QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // One should still be able to move the client around. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); client->keyPressEvent(Qt::Key_Right); client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(client->pos(), QPoint(50, 42)); client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QCOMPARE(client->pos(), QPoint(50, 42)); // The rule should be applied again if the client appears after it's been closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testPositionRemember() { // Initialize RuleBook with the test rule. setWindowRule("position", QPoint(42, 42), int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The client should be moved to the position specified by the rule. QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // One should still be able to move the client around. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); client->keyPressEvent(Qt::Key_Right); client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(client->pos(), QPoint(50, 42)); client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QCOMPARE(client->pos(), QPoint(50, 42)); // The client should be placed at the last know position if we reopen it. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(50, 42)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testPositionForce() { setWindowRule("position", QPoint(42, 42), int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The client should be moved to the position specified by the rule. QVERIFY(!client->isMovable()); QVERIFY(!client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // User should not be able to move the client. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); // The position should still be forced if we reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isMovable()); QVERIFY(!client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testPositionApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; QObject *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The position of the client isn't set by any rule, thus the default placement // policy will try to put the client in the top-left corner of the screen. QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(0, 0)); QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); setWindowRule("position", QPoint(42, 42), int(Rules::ApplyNow)); // The client should be moved to the position specified by the rule. QCOMPARE(frameGeometryChangedSpy.count(), 1); QCOMPARE(client->pos(), QPoint(42, 42)); // We still have to be able to move the client around. QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); client->keyPressEvent(Qt::Key_Right); client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(client->pos(), QPoint(50, 42)); client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QCOMPARE(client->pos(), QPoint(50, 42)); // The rule should not be applied again. client->evaluateWindowRules(); QCOMPARE(client->pos(), QPoint(50, 42)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testPositionForceTemporarily() { setWindowRule("position", QPoint(42, 42), int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The client should be moved to the position specified by the rule. QVERIFY(!client->isMovable()); QVERIFY(!client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(42, 42)); // User should not be able to move the client. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); // The rule should be discarded if we close the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMovable()); QVERIFY(client->isMovableAcrossScreens()); QCOMPARE(client->pos(), QPoint(0, 0)); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeDontAffect() { setWindowRule("size", QSize(480, 640), int(Rules::DontAffect)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The window size shouldn't be enforced by the rule. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeApply() { setWindowRule("size", QSize(480, 640), int(Rules::Apply)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The initial configure event should contain size hint set by the rule. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(480, 640)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // One still should be able to resize the client. QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(!client->isInteractiveMove()); QVERIFY(client->isInteractiveResize()); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); client->keyPressEvent(Qt::Key_Right); client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(toplevelConfigureRequestedSpy->count(), 4); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(488, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 5); QCOMPARE(toplevelConfigureRequestedSpy->count(), 5); // The rule should be applied again if the client appears after it's been closed. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeRemember() { setWindowRule("size", QSize(480, 640), int(Rules::Remember)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The initial configure event should contain size hint set by the rule. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // One should still be able to resize the client. QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(!client->isInteractiveMove()); QVERIFY(client->isInteractiveResize()); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); client->keyPressEvent(Qt::Key_Right); client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(toplevelConfigureRequestedSpy->count(), 4); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(488, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 5); QCOMPARE(toplevelConfigureRequestedSpy->count(), 5); // If the client appears again, it should have the last known size. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(488, 640)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(488, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(488, 640)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeForce() { setWindowRule("size", QSize(480, 640), int(Rules::Force)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The initial configure event should contain size hint set by the rule. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Any attempt to resize the client should not succeed. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); // If the client appears again, the size should still be forced. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeApplyNow() { // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The expected surface dimensions should be set by the rule. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); setWindowRule("size", QSize(480, 640), int(Rules::ApplyNow)); // The compositor should send a configure event with a new size. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Draw the surface with the new size. QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(480, 640)); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSizeForceTemporarily() { setWindowRule("size", QSize(480, 640), int(Rules::ForceTemporarily)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // The initial configure event should contain size hint set by the rule. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(480, 640), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isResizable()); QCOMPARE(client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Any attempt to resize the client should not succeed. QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!client->isInteractiveMove()); QVERIFY(!client->isInteractiveResize()); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); // The rule should be discarded when the client is closed. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isResizable()); QCOMPARE(client->size(), QSize(100, 50)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeDontAffect() { setWindowRule("maximizehoriz", true, int(Rules::DontAffect)); setWindowRule("maximizevert", true, int(Rules::DontAffect)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeApply() { setWindowRule("maximizehoriz", true, int(Rules::Apply)); setWindowRule("maximizevert", true, int(Rules::Apply)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // One should still be able to change the maximized state of the client. workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(100, 50)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // If we create the client again, it should be initially maximized. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeRemember() { setWindowRule("maximizehoriz", true, int(Rules::Remember)); setWindowRule("maximizevert", true, int(Rules::Remember)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // One should still be able to change the maximized state of the client. workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(100, 50)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // If we create the client again, it should not be maximized (because last time it wasn't). shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->size(), QSize(100, 50)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeForce() { setWindowRule("maximizehoriz", true, int(Rules::Force)); setWindowRule("maximizevert", true, int(Rules::Force)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Any attempt to change the maximized state should not succeed. const QRect oldGeometry = client->frameGeometry(); workspace()->slotWindowMaximize(); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->frameGeometry(), oldGeometry); // If we create the client again, the maximized state should still be forced. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeApplyNow() { // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); setWindowRule("maximizehoriz", true, int(Rules::ApplyNow)); setWindowRule("maximizevert", true, int(Rules::ApplyNow)); // We should receive a configure event with a new surface size. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Draw contents of the maximized client. QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(1280, 1024)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); // The client still has to be maximizeable. QVERIFY(client->isMaximizable()); // Restore the client. workspace()->slotWindowMaximize(); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(toplevelConfigureRequestedSpy->count(), 4); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(100, 50)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(client->size(), QSize(100, 50)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // The rule should be discarded after it's been applied. const QRect oldGeometry = client->frameGeometry(); client->evaluateWindowRules(); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->frameGeometry(), oldGeometry); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMaximizeForceTemporarily() { setWindowRule("maximizehoriz", true, int(Rules::ForceTemporarily)); setWindowRule("maximizevert", true, int(Rules::ForceTemporarily)); // Create the test client. QScopedPointer surface; surface.reset(Test::createSurface()); QScopedPointer shellSurface; shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); QScopedPointer toplevelConfigureRequestedSpy; toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); QScopedPointer surfaceConfigureRequestedSpy; surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); // Wait for the initial configure event. Test::XdgToplevel::States states; QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(!client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Any attempt to change the maximized state should not succeed. const QRect oldGeometry = client->frameGeometry(); workspace()->slotWindowMaximize(); QVERIFY(!surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(client->frameGeometry(), oldGeometry); // The rule should be discarded if we close the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); surface.reset(Test::createSurface()); shellSurface.reset(Test::createXdgToplevelSurface(surface.data(), Test::CreationSetup::CreateOnly)); toplevelConfigureRequestedSpy.reset(new QSignalSpy(shellSurface.data(), &Test::XdgToplevel::configureRequested)); surfaceConfigureRequestedSpy.reset(new QSignalSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); shellSurface->set_app_id(QStringLiteral("org.kde.foo")); surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy->last().at(0).value()); client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); QVERIFY(client); QVERIFY(client->isActive()); QVERIFY(client->isMaximizable()); QCOMPARE(client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(client->size(), QSize(100, 50)); QVERIFY(surfaceConfigureRequestedSpy->wait()); QCOMPARE(surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(toplevelConfigureRequestedSpy->count(), 2); states = toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Destroy the client. shellSurface.reset(); surface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopDontAffect() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should appear on the current virtual desktop. QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopApply() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should appear on the second virtual desktop. QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // We still should be able to move the client between desktops. workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // If we re-open the client, it should appear on the second virtual desktop again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopRemember() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // Move the client to the first virtual desktop. workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // If we create the client again, it should appear on the first virtual desktop. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopForce() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should appear on the second virtual desktop. QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // Any attempt to move the client to another virtual desktop should fail. workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // If we re-open the client, it should appear on the second virtual desktop again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopApplyNow() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::ApplyNow)); // The client should have been moved to the second virtual desktop. QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // One should still be able to move the client between desktops. workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // The rule should not be applied again. client->evaluateWindowRules(); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopForceTemporarily() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); setWindowRule("desktops", QStringList{VirtualDesktopManager::self()->desktopForX11Id(2)->id()}, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should appear on the second virtual desktop. QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // Any attempt to move the client to another virtual desktop should fail. workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 2); // The rule should be discarded when the client is withdrawn. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // One should be able to move the client between desktops. workspace()->sendClientToDesktop(client, 2, true); QCOMPARE(client->desktop(), 2); QCOMPARE(VirtualDesktopManager::self()->current(), 1); workspace()->sendClientToDesktop(client, 1, true); QCOMPARE(client->desktop(), 1); QCOMPARE(VirtualDesktopManager::self()->current(), 1); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeDontAffect() { setWindowRule("minimize", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); // The client should not be minimized. QVERIFY(!client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeApply() { setWindowRule("minimize", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); // The client should be minimized. QVERIFY(client->isMinimized()); // We should still be able to unminimize the client. client->unminimize(); QVERIFY(!client->isMinimized()); // If we re-open the client, it should be minimized back again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); QVERIFY(client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeRemember() { setWindowRule("minimize", false, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); QVERIFY(!client->isMinimized()); // Minimize the client. client->minimize(); QVERIFY(client->isMinimized()); // If we open the client again, it should be minimized. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); QVERIFY(client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeForce() { setWindowRule("minimize", false, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->isMinimizable()); QVERIFY(!client->isMinimized()); // Any attempt to minimize the client should fail. client->minimize(); QVERIFY(!client->isMinimized()); // If we re-open the client, the minimized state should still be forced. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->isMinimizable()); QVERIFY(!client->isMinimized()); client->minimize(); QVERIFY(!client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); QVERIFY(!client->isMinimized()); setWindowRule("minimize", true, int(Rules::ApplyNow)); // The client should be minimized now. QVERIFY(client->isMinimizable()); QVERIFY(client->isMinimized()); // One is still able to unminimize the client. client->unminimize(); QVERIFY(!client->isMinimized()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(client->isMinimizable()); QVERIFY(!client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMinimizeForceTemporarily() { setWindowRule("minimize", false, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->isMinimizable()); QVERIFY(!client->isMinimized()); // Any attempt to minimize the client should fail until the client is closed. client->minimize(); QVERIFY(!client->isMinimized()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isMinimizable()); QVERIFY(!client->isMinimized()); client->minimize(); QVERIFY(client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarDontAffect() { setWindowRule("skiptaskbar", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be affected by the rule. QVERIFY(!client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarApply() { setWindowRule("skiptaskbar", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a taskbar. QVERIFY(client->skipTaskbar()); // Though one can change that. client->setOriginalSkipTaskbar(false); QVERIFY(!client->skipTaskbar()); // Reopen the client, the rule should be applied again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarRemember() { setWindowRule("skiptaskbar", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a taskbar. QVERIFY(client->skipTaskbar()); // Change the skip-taskbar state. client->setOriginalSkipTaskbar(false); QVERIFY(!client->skipTaskbar()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be included on a taskbar. QVERIFY(!client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarForce() { setWindowRule("skiptaskbar", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a taskbar. QVERIFY(client->skipTaskbar()); // Any attempt to change the skip-taskbar state should not succeed. client->setOriginalSkipTaskbar(false); QVERIFY(client->skipTaskbar()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The skip-taskbar state should be still forced. QVERIFY(client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipTaskbar()); setWindowRule("skiptaskbar", true, int(Rules::ApplyNow)); // The client should not be on a taskbar now. QVERIFY(client->skipTaskbar()); // Also, one change the skip-taskbar state. client->setOriginalSkipTaskbar(false); QVERIFY(!client->skipTaskbar()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipTaskbarForceTemporarily() { setWindowRule("skiptaskbar", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a taskbar. QVERIFY(client->skipTaskbar()); // Any attempt to change the skip-taskbar state should not succeed. client->setOriginalSkipTaskbar(false); QVERIFY(client->skipTaskbar()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipTaskbar()); // The skip-taskbar state is no longer forced. client->setOriginalSkipTaskbar(true); QVERIFY(client->skipTaskbar()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerDontAffect() { setWindowRule("skippager", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be affected by the rule. QVERIFY(!client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerApply() { setWindowRule("skippager", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a pager. QVERIFY(client->skipPager()); // Though one can change that. client->setSkipPager(false); QVERIFY(!client->skipPager()); // Reopen the client, the rule should be applied again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerRemember() { setWindowRule("skippager", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a pager. QVERIFY(client->skipPager()); // Change the skip-pager state. client->setSkipPager(false); QVERIFY(!client->skipPager()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be included on a pager. QVERIFY(!client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerForce() { setWindowRule("skippager", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a pager. QVERIFY(client->skipPager()); // Any attempt to change the skip-pager state should not succeed. client->setSkipPager(false); QVERIFY(client->skipPager()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The skip-pager state should be still forced. QVERIFY(client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipPager()); setWindowRule("skippager", true, int(Rules::ApplyNow)); // The client should not be on a pager now. QVERIFY(client->skipPager()); // Also, one change the skip-pager state. client->setSkipPager(false); QVERIFY(!client->skipPager()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipPagerForceTemporarily() { setWindowRule("skippager", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be included on a pager. QVERIFY(client->skipPager()); // Any attempt to change the skip-pager state should not succeed. client->setSkipPager(false); QVERIFY(client->skipPager()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipPager()); // The skip-pager state is no longer forced. client->setSkipPager(true); QVERIFY(client->skipPager()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherDontAffect() { setWindowRule("skipswitcher", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be affected by the rule. QVERIFY(!client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherApply() { setWindowRule("skipswitcher", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be excluded from window switching effects. QVERIFY(client->skipSwitcher()); // Though one can change that. client->setSkipSwitcher(false); QVERIFY(!client->skipSwitcher()); // Reopen the client, the rule should be applied again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherRemember() { setWindowRule("skipswitcher", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be excluded from window switching effects. QVERIFY(client->skipSwitcher()); // Change the skip-switcher state. client->setSkipSwitcher(false); QVERIFY(!client->skipSwitcher()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be included in window switching effects. QVERIFY(!client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherForce() { setWindowRule("skipswitcher", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be excluded from window switching effects. QVERIFY(client->skipSwitcher()); // Any attempt to change the skip-switcher state should not succeed. client->setSkipSwitcher(false); QVERIFY(client->skipSwitcher()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The skip-switcher state should be still forced. QVERIFY(client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipSwitcher()); setWindowRule("skipswitcher", true, int(Rules::ApplyNow)); // The client should be excluded from window switching effects now. QVERIFY(client->skipSwitcher()); // Also, one change the skip-switcher state. client->setSkipSwitcher(false); QVERIFY(!client->skipSwitcher()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testSkipSwitcherForceTemporarily() { setWindowRule("skipswitcher", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be excluded from window switching effects. QVERIFY(client->skipSwitcher()); // Any attempt to change the skip-switcher state should not succeed. client->setSkipSwitcher(false); QVERIFY(client->skipSwitcher()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->skipSwitcher()); // The skip-switcher state is no longer forced. client->setSkipSwitcher(true); QVERIFY(client->skipSwitcher()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveDontAffect() { setWindowRule("above", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The keep-above state of the client should not be affected by the rule. QVERIFY(!client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveApply() { setWindowRule("above", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept above. QVERIFY(client->keepAbove()); // One should also be able to alter the keep-above state. client->setKeepAbove(false); QVERIFY(!client->keepAbove()); // If one re-opens the client, it should be kept above back again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveRemember() { setWindowRule("above", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept above. QVERIFY(client->keepAbove()); // Unset the keep-above state. client->setKeepAbove(false); QVERIFY(!client->keepAbove()); delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); // Re-open the client, it should not be kept above. std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveForce() { setWindowRule("above", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept above. QVERIFY(client->keepAbove()); // Any attemt to unset the keep-above should not succeed. client->setKeepAbove(false); QVERIFY(client->keepAbove()); // If we re-open the client, it should still be kept above. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepAbove()); setWindowRule("above", true, int(Rules::ApplyNow)); // The client should now be kept above other clients. QVERIFY(client->keepAbove()); // One is still able to change the keep-above state of the client. client->setKeepAbove(false); QVERIFY(!client->keepAbove()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepAboveForceTemporarily() { setWindowRule("above", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept above. QVERIFY(client->keepAbove()); // Any attempt to alter the keep-above state should not succeed. client->setKeepAbove(false); QVERIFY(client->keepAbove()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepAbove()); // The keep-above state is no longer forced. client->setKeepAbove(true); QVERIFY(client->keepAbove()); client->setKeepAbove(false); QVERIFY(!client->keepAbove()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowDontAffect() { setWindowRule("below", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The keep-below state of the client should not be affected by the rule. QVERIFY(!client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowApply() { setWindowRule("below", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept below. QVERIFY(client->keepBelow()); // One should also be able to alter the keep-below state. client->setKeepBelow(false); QVERIFY(!client->keepBelow()); // If one re-opens the client, it should be kept above back again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowRemember() { setWindowRule("below", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept below. QVERIFY(client->keepBelow()); // Unset the keep-below state. client->setKeepBelow(false); QVERIFY(!client->keepBelow()); delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); // Re-open the client, it should not be kept below. std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowForce() { setWindowRule("below", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept below. QVERIFY(client->keepBelow()); // Any attemt to unset the keep-below should not succeed. client->setKeepBelow(false); QVERIFY(client->keepBelow()); // If we re-open the client, it should still be kept below. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepBelow()); setWindowRule("below", true, int(Rules::ApplyNow)); // The client should now be kept below other clients. QVERIFY(client->keepBelow()); // One is still able to change the keep-below state of the client. client->setKeepBelow(false); QVERIFY(!client->keepBelow()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testKeepBelowForceTemporarily() { setWindowRule("below", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be kept below. QVERIFY(client->keepBelow()); // Any attempt to alter the keep-below state should not succeed. client->setKeepBelow(false); QVERIFY(client->keepBelow()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(!client->keepBelow()); // The keep-below state is no longer forced. client->setKeepBelow(true); QVERIFY(client->keepBelow()); client->setKeepBelow(false); QVERIFY(!client->keepBelow()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutDontAffect() { setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->shortcut(), QKeySequence()); client->minimize(); QVERIFY(client->isMinimized()); // If we press the window shortcut, nothing should happen. QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(client->isMinimized()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutApply() { setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // One can also change the shortcut. client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // The old shortcut should do nothing. client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(client->isMinimized()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The window shortcut should be set back to Ctrl+Alt+1. QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutRemember() { QSKIP("KWin core doesn't try to save the last used window shortcut"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // Change the window shortcut to Ctrl+Alt+2. client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The window shortcut should be set to the last known value. QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutForce() { QSKIP("KWin core can't release forced window shortcuts"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // Any attempt to change the window shortcut should not succeed. client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(client->isMinimized()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The window shortcut should still be forced. QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->shortcut().isEmpty()); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ApplyNow)); // The client should now have a window shortcut assigned. QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // Assign a different shortcut. client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // The rule should not be applied again. client->evaluateWindowRules(); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testShortcutForceTemporarily() { QSKIP("KWin core can't release forced window shortcuts"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!client->isMinimized()); // Any attempt to change the window shortcut should not succeed. client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); client->minimize(); QVERIFY(client->isMinimized()); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(client->isMinimized()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->shortcut().isEmpty()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testDesktopFileDontAffect() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testDesktopFileApply() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testDesktopFileRemember() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testDesktopFileForce() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testDesktopFileApplyNow() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testDesktopFileForceTemporarily() { // Currently, the desktop file name is derived from the app id. If the app id is // changed, then the old rules will be lost. Either setDesktopFileName should // be exposed or the desktop file name rule should be removed for wayland clients. QSKIP("Needs changes in KWin core to pass"); } void TestXdgShellClientRules::testActiveOpacityDontAffect() { setWindowRule("opacityactive", 90, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // The opacity should not be affected by the rule. QCOMPARE(client->opacity(), 1.0); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testActiveOpacityForce() { setWindowRule("opacityactive", 90, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 0.9); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testActiveOpacityForceTemporarily() { setWindowRule("opacityactive", 90, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 0.9); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 1.0); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testInactiveOpacityDontAffect() { setWindowRule("opacityinactive", 80, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!client->isActive()); // The opacity of the client should not be affected by the rule. QCOMPARE(client->opacity(), 1.0); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testInactiveOpacityForce() { setWindowRule("opacityinactive", 80, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 1.0); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!client->isActive()); // The opacity should be forced by the rule. QCOMPARE(client->opacity(), 0.8); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testInactiveOpacityForceTemporarily() { setWindowRule("opacityinactive", 80, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 1.0); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!client->isActive()); // The opacity should be forced by the rule. QCOMPARE(client->opacity(), 0.8); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); QCOMPARE(client->opacity(), 1.0); workspace()->setActiveClient(nullptr); QVERIFY(!client->isActive()); QCOMPARE(client->opacity(), 1.0); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderDontAffect() { // Initialize RuleBook with the test rule. setWindowRule("noborder", true, int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // The client should not be affected by the rule. QVERIFY(!client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderApply() { setWindowRule("noborder", true, int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // Initially, the client should not be decorated. QVERIFY(client->noBorder()); QVERIFY(!client->isDecorated()); // But you should be able to change "no border" property afterwards. QVERIFY(client->userCanSetNoBorder()); client->setNoBorder(false); QVERIFY(!client->noBorder()); // If one re-opens the client, it should have no border again. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); QVERIFY(client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderRemember() { setWindowRule("noborder", true, int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // Initially, the client should not be decorated. QVERIFY(client->noBorder()); QVERIFY(!client->isDecorated()); // Unset the "no border" property. QVERIFY(client->userCanSetNoBorder()); client->setNoBorder(false); QVERIFY(!client->noBorder()); delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); // Re-open the client, it should be decorated. std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); QVERIFY(client->isDecorated()); QVERIFY(!client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderForce() { setWindowRule("noborder", true, int(Rules::Force)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // The client should not be decorated. QVERIFY(client->noBorder()); QVERIFY(!client->isDecorated()); // And the user should not be able to change the "no border" property. client->setNoBorder(false); QVERIFY(client->noBorder()); // Reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // The "no border" property should be still forced. QVERIFY(client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderApplyNow() { // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); QVERIFY(!client->noBorder()); // Initialize RuleBook with the test rule. setWindowRule("noborder", true, int(Rules::ApplyNow)); // The "no border" property should be set now. QVERIFY(client->noBorder()); // One should be still able to change the "no border" property. client->setNoBorder(false); QVERIFY(!client->noBorder()); // The rule should not be applied again. client->evaluateWindowRules(); QVERIFY(!client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testNoBorderForceTemporarily() { setWindowRule("noborder", true, int(Rules::ForceTemporarily)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); // The "no border" property should be set. QVERIFY(client->noBorder()); // And you should not be able to change it. client->setNoBorder(false); QVERIFY(client->noBorder()); // The rule should be discarded when the client is closed. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo"), Test::XdgToplevelDecorationV1::mode_server_side); QVERIFY(client); QVERIFY(!client->noBorder()); // The "no border" property is no longer forced. client->setNoBorder(true); QVERIFY(client->noBorder()); client->setNoBorder(false); QVERIFY(!client->noBorder()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenDontAffect() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::DontAffect)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should not be affected by the rule. QCOMPARE(client->output()->name(), outputs.at(0)->name()); // The user can still move the client to another screen. workspace()->sendClientToOutput(client, outputs.at(1)); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenApply() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::Apply)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The client should be in the screen specified by the rule. QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // The user can move the client to another screen. workspace()->sendClientToOutput(client, outputs.at(0)); QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenRemember() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::Remember)); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // Initially, the client should be in the first screen QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Move the client to the second screen. workspace()->sendClientToOutput(client, outputs.at(1)); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenForce() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QVERIFY(client->isActive()); setWindowRule("screen", int(1), int(Rules::Force)); // The client should be forced to the screen specified by the rule. QCOMPARE(client->output()->name(), outputs.at(1)->name()); // User should not be able to move the client to another screen. workspace()->sendClientToOutput(client, outputs.at(0)); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Disable the output where the window is on, so the client is moved the other screen WaylandOutputConfig config; auto changeSet = config.changeSet(static_cast(outputs.at(1))); changeSet->enabled = false; kwinApp()->platform()->applyOutputChanges(config); QVERIFY(!outputs.at(1)->isEnabled()); QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Enable the output and check that the client is moved there again changeSet->enabled = true; kwinApp()->platform()->applyOutputChanges(config); QVERIFY(outputs.at(1)->isEnabled()); QEXPECT_FAIL("", "Disabling and enabling an output does not move the client to the Forced screen", Continue); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenApplyNow() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Set the rule so the client should move to the screen specified by the rule. setWindowRule("screen", int(1), int(Rules::ApplyNow)); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // The user can move the client to another screen. workspace()->sendClientToOutput(client, outputs.at(0)); QCOMPARE(client->output()->name(), outputs.at(0)->name()); // The rule should not be applied again. client->evaluateWindowRules(); QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testScreenForceTemporarily() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); // Create the test client. AbstractClient *client; KWayland::Client::Surface *surface; Test::XdgToplevel *shellSurface; std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); setWindowRule("screen", int(1), int(Rules::ForceTemporarily)); // The client should be forced the second screen QCOMPARE(client->output()->name(), outputs.at(1)->name()); // User is not allowed to move it workspace()->sendClientToOutput(client, outputs.at(0)); QCOMPARE(client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); std::tie(client, surface, shellSurface) = createWindow(QStringLiteral("org.kde.foo")); QVERIFY(client); // The rule should be discarded now QCOMPARE(client->output()->name(), outputs.at(0)->name()); // Destroy the client. delete shellSurface; delete surface; QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClientRules::testMatchAfterNameChange() { setWindowRule("above", true, int(Rules::Force)); QScopedPointer surface(Test::createSurface()); QScopedPointer shellSurface(Test::createXdgToplevelSurface(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->set_app_id(QStringLiteral("org.kde.foo")); QVERIFY(desktopFileNameSpy.wait()); QCOMPARE(c->keepAbove(), true); } WAYLANDTEST_MAIN(TestXdgShellClientRules) #include "xdgshellclient_rules_test.moc"