/* 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-FileCopyrightText: 2022 Ismael Asensio 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 enum ClientFlag { None = 0, ClientShouldBeInactive = 1 << 0, // Client should be inactive. Used on Minimize tests ServerSideDecoration = 1 << 1, // Create window with server side decoration. Used on noBorder tests ReturnAfterSurfaceConfiguration = 1 << 2, // Do not create the client now, but return after surface configuration. }; Q_DECLARE_FLAGS(ClientFlags, ClientFlag); 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 testDesktopsDontAffect(); void testDesktopsApply(); void testDesktopsRemember(); void testDesktopsForce(); void testDesktopsApplyNow(); void testDesktopsForceTemporarily(); 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: void createTestWindow(ClientFlags flags = None); void mapClientToSurface(QSize clientSize, ClientFlags flags = None); void destroyTestWindow(); template void setWindowRule(const QString &property, const T &value, int policy); private: KSharedConfig::Ptr m_config; AbstractClient *m_client; QScopedPointer m_surface; QScopedPointer m_shellSurface; QScopedPointer m_toplevelConfigureRequestedSpy; QScopedPointer m_surfaceConfigureRequestedSpy; }; 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() { if (!m_shellSurface.isNull()) { destroyTestWindow(); } 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); } void TestXdgShellClientRules::createTestWindow(ClientFlags flags) { // Apply flags for special windows and rules const bool createClient = !(flags & ReturnAfterSurfaceConfiguration); const auto decorationMode = (flags & ServerSideDecoration) ? Test::XdgToplevelDecorationV1::mode_server_side : Test::XdgToplevelDecorationV1::mode_client_side; // Create an xdg surface. m_surface.reset(Test::createSurface()); m_shellSurface.reset(Test::createXdgToplevelSurface(m_surface.data(), Test::CreationSetup::CreateOnly, m_surface.data())); Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(m_shellSurface.data(), m_shellSurface.data()); // Add signal watchers m_toplevelConfigureRequestedSpy.reset(new QSignalSpy(m_shellSurface.data(), &Test::XdgToplevel::configureRequested)); m_surfaceConfigureRequestedSpy.reset(new QSignalSpy(m_shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); m_shellSurface->set_app_id(QStringLiteral("org.kde.foo")); decoration->set_mode(decorationMode); // Wait for the initial configure event m_surface->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); if (createClient) { mapClientToSurface(QSize(100, 50), flags); } } void TestXdgShellClientRules::mapClientToSurface(QSize clientSize, ClientFlags flags) { const bool clientShouldBeActive = !(flags & ClientShouldBeInactive); QVERIFY(!m_surface.isNull()); QVERIFY(!m_shellSurface.isNull()); QVERIFY(!m_surfaceConfigureRequestedSpy.isNull()); // Draw content of the surface. m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); // Create the client m_client = Test::renderAndWaitForShown(m_surface.data(), clientSize, Qt::blue); QVERIFY(m_client); QCOMPARE(m_client->isActive(), clientShouldBeActive); } void TestXdgShellClientRules::destroyTestWindow() { m_surfaceConfigureRequestedSpy.reset(); m_toplevelConfigureRequestedSpy.reset(); m_shellSurface.reset(); m_surface.reset(); QVERIFY(Test::waitForWindowDestroyed(m_client)); } 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)); createTestWindow(); // 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(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(0, 0)); destroyTestWindow(); } void TestXdgShellClientRules::testPositionApply() { setWindowRule("position", QPoint(42, 42), int(Rules::Apply)); createTestWindow(); // The client should be moved to the position specified by the rule. QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); // One should still be able to move the client around. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(m_client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(m_client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), m_client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); m_client->keyPressEvent(Qt::Key_Right); m_client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(m_client->pos(), QPoint(50, 42)); m_client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QCOMPARE(m_client->pos(), QPoint(50, 42)); // The rule should be applied again if the client appears after it's been closed. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); destroyTestWindow(); } void TestXdgShellClientRules::testPositionRemember() { setWindowRule("position", QPoint(42, 42), int(Rules::Remember)); createTestWindow(); // The client should be moved to the position specified by the rule. QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); // One should still be able to move the client around. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(m_client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(m_client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), m_client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); m_client->keyPressEvent(Qt::Key_Right); m_client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(m_client->pos(), QPoint(50, 42)); m_client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QCOMPARE(m_client->pos(), QPoint(50, 42)); // The client should be placed at the last know position if we reopen it. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(50, 42)); destroyTestWindow(); } void TestXdgShellClientRules::testPositionForce() { setWindowRule("position", QPoint(42, 42), int(Rules::Force)); createTestWindow(); // The client should be moved to the position specified by the rule. QVERIFY(!m_client->isMovable()); QVERIFY(!m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); // User should not be able to move the client. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); // The position should still be forced if we reopen the client. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->isMovable()); QVERIFY(!m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); destroyTestWindow(); } void TestXdgShellClientRules::testPositionApplyNow() { createTestWindow(); // 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(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(0, 0)); QSignalSpy frameGeometryChangedSpy(m_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(m_client->pos(), QPoint(42, 42)); // We still have to be able to move the client around. QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(m_client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(m_client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), m_client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); m_client->keyPressEvent(Qt::Key_Right); m_client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); QCOMPARE(m_client->pos(), QPoint(50, 42)); m_client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QCOMPARE(m_client->pos(), QPoint(50, 42)); // The rule should not be applied again. m_client->evaluateWindowRules(); QCOMPARE(m_client->pos(), QPoint(50, 42)); destroyTestWindow(); } void TestXdgShellClientRules::testPositionForceTemporarily() { setWindowRule("position", QPoint(42, 42), int(Rules::ForceTemporarily)); createTestWindow(); // The client should be moved to the position specified by the rule. QVERIFY(!m_client->isMovable()); QVERIFY(!m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(42, 42)); // User should not be able to move the client. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowMove(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); // The rule should be discarded if we close the client. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isMovable()); QVERIFY(m_client->isMovableAcrossScreens()); QCOMPARE(m_client->pos(), QPoint(0, 0)); destroyTestWindow(); } void TestXdgShellClientRules::testSizeDontAffect() { setWindowRule("size", QSize(480, 640), int(Rules::DontAffect)); createTestWindow(ReturnAfterSurfaceConfiguration); // The window size shouldn't be enforced by the rule. QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); // Map the client. mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); destroyTestWindow(); } void TestXdgShellClientRules::testSizeApply() { setWindowRule("size", QSize(480, 640), int(Rules::Apply)); createTestWindow(ReturnAfterSurfaceConfiguration); // The initial configure event should contain size hint set by the rule. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(480, 640)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // Map the client. mapClientToSurface(QSize(480, 640)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(m_client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(m_client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), m_client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(m_client->isInteractiveResize()); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); m_client->keyPressEvent(Qt::Key_Right); m_client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(488, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); m_client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 5); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 5); // The rule should be applied again if the client appears after it's been closed. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); mapClientToSurface(QSize(480, 640)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); destroyTestWindow(); } void TestXdgShellClientRules::testSizeRemember() { setWindowRule("size", QSize(480, 640), int(Rules::Remember)); createTestWindow(ReturnAfterSurfaceConfiguration); // The initial configure event should contain size hint set by the rule. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); // Map the client. mapClientToSurface(QSize(480, 640)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QSignalSpy clientStepUserMovedResizedSpy(m_client, &AbstractClient::clientStepUserMovedResized); QVERIFY(clientStepUserMovedResizedSpy.isValid()); QSignalSpy clientFinishUserMovedResizedSpy(m_client, &AbstractClient::clientFinishUserMovedResized); QVERIFY(clientFinishUserMovedResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), m_client); QCOMPARE(clientStartMoveResizedSpy.count(), 1); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(m_client->isInteractiveResize()); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); m_client->keyPressEvent(Qt::Key_Right); m_client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(488, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(488, 640)); QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); m_client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 5); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 5); // If the client appears again, it should have the last known size. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(488, 640)); mapClientToSurface(QSize(488, 640)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(488, 640)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); destroyTestWindow(); } void TestXdgShellClientRules::testSizeForce() { setWindowRule("size", QSize(480, 640), int(Rules::Force)); createTestWindow(ReturnAfterSurfaceConfiguration); // The initial configure event should contain size hint set by the rule. QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Map the client. mapClientToSurface(QSize(480, 640)); QVERIFY(!m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); // Any attempt to resize the client should not succeed. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); // If the client appears again, the size should still be forced. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); mapClientToSurface(QSize(480, 640)); QVERIFY(!m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); destroyTestWindow(); } void TestXdgShellClientRules::testSizeApplyNow() { createTestWindow(ReturnAfterSurfaceConfiguration); // The expected surface dimensions should be set by the rule. QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); // Map the client. mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); setWindowRule("size", QSize(480, 640), int(Rules::ApplyNow)); // The compositor should send a configure event with a new size. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Draw the surface with the new size. QSignalSpy frameGeometryChangedSpy(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(480, 640), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(480, 640)); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); destroyTestWindow(); } void TestXdgShellClientRules::testSizeForceTemporarily() { setWindowRule("size", QSize(480, 640), int(Rules::ForceTemporarily)); createTestWindow(ReturnAfterSurfaceConfiguration); // The initial configure event should contain size hint set by the rule. QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); // Map the client. mapClientToSurface(QSize(480, 640)); QVERIFY(!m_client->isResizable()); QCOMPARE(m_client->size(), QSize(480, 640)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); // Any attempt to resize the client should not succeed. QSignalSpy clientStartMoveResizedSpy(m_client, &AbstractClient::clientStartUserMovedResized); QVERIFY(clientStartMoveResizedSpy.isValid()); QCOMPARE(workspace()->moveResizeClient(), nullptr); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); workspace()->slotWindowResize(); QCOMPARE(workspace()->moveResizeClient(), nullptr); QCOMPARE(clientStartMoveResizedSpy.count(), 0); QVERIFY(!m_client->isInteractiveMove()); QVERIFY(!m_client->isInteractiveResize()); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isResizable()); QCOMPARE(m_client->size(), QSize(100, 50)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeDontAffect() { setWindowRule("maximizehoriz", true, int(Rules::DontAffect)); setWindowRule("maximizevert", true, int(Rules::DontAffect)); createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeApply() { setWindowRule("maximizehoriz", true, int(Rules::Apply)); setWindowRule("maximizevert", true, int(Rules::Apply)); createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(1280, 1024)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); QSignalSpy frameGeometryChangedSpy(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(100, 50)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // If we create the client again, it should be initially maximized. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); mapClientToSurface(QSize(1280, 1024)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeRemember() { setWindowRule("maximizehoriz", true, int(Rules::Remember)); setWindowRule("maximizevert", true, int(Rules::Remember)); createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(1280, 1024)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); QSignalSpy frameGeometryChangedSpy(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(100, 50)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // If we create the client again, it should not be maximized (because last time it wasn't). destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->size(), QSize(100, 50)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeForce() { setWindowRule("maximizehoriz", true, int(Rules::Force)); setWindowRule("maximizevert", true, int(Rules::Force)); createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(1280, 1024)); QVERIFY(!m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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 = m_client->frameGeometry(); workspace()->slotWindowMaximize(); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->frameGeometry(), oldGeometry); // If we create the client again, the maximized state should still be forced. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); mapClientToSurface(QSize(1280, 1024)); QVERIFY(!m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeApplyNow() { createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->size(), QSize(100, 50)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_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(m_client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(1280, 1024), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(1280, 1024)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); // The client still has to be maximizeable. QVERIFY(m_client->isMaximizable()); // Restore the client. workspace()->slotWindowMaximize(); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(100, 50)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); Test::render(m_surface.data(), QSize(100, 50), Qt::blue); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(m_client->size(), QSize(100, 50)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); // The rule should be discarded after it's been applied. const QRect oldGeometry = m_client->frameGeometry(); m_client->evaluateWindowRules(); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->frameGeometry(), oldGeometry); destroyTestWindow(); } void TestXdgShellClientRules::testMaximizeForceTemporarily() { setWindowRule("maximizehoriz", true, int(Rules::ForceTemporarily)); setWindowRule("maximizevert", true, int(Rules::ForceTemporarily)); createTestWindow(ReturnAfterSurfaceConfiguration); // Wait for the initial configure event. Test::XdgToplevel::States states; QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); // Map the client. mapClientToSurface(QSize(1280, 1024)); QVERIFY(!m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->size(), QSize(1280, 1024)); // We should receive a configure event when the client becomes active. QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_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 = m_client->frameGeometry(); workspace()->slotWindowMaximize(); QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeFull); QCOMPARE(m_client->frameGeometry(), oldGeometry); // The rule should be discarded if we close the client. destroyTestWindow(); createTestWindow(ReturnAfterSurfaceConfiguration); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); mapClientToSurface(QSize(100, 50)); QVERIFY(m_client->isMaximizable()); QCOMPARE(m_client->maximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); QCOMPARE(m_client->size(), QSize(100, 50)); QVERIFY(m_surfaceConfigureRequestedSpy->wait()); QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); states = m_toplevelConfigureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsDontAffect() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::DontAffect)); createTestWindow(); // The client should appear on the current virtual desktop. QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsApply() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Apply)); createTestWindow(); // The client should appear on the second virtual desktop. QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // We still should be able to move the client between desktops. m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // If we re-open the client, it should appear on the second virtual desktop again. destroyTestWindow(); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); createTestWindow(); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsRemember() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Remember)); createTestWindow(); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // Move the client to the first virtual desktop. m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // If we create the client again, it should appear on the first virtual desktop. destroyTestWindow(); createTestWindow(); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsForce() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Force)); createTestWindow(); // The client should appear on the second virtual desktop. QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // Any attempt to move the client to another virtual desktop should fail. m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // If we re-open the client, it should appear on the second virtual desktop again. destroyTestWindow(); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); createTestWindow(); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsApplyNow() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); createTestWindow(); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::ApplyNow)); // The client should have been moved to the second virtual desktop. QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); // One should still be able to move the client between desktops. m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); // The rule should not be applied again. m_client->evaluateWindowRules(); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); destroyTestWindow(); } void TestXdgShellClientRules::testDesktopsForceTemporarily() { // We need at least two virtual desktop for this test. VirtualDesktopManager::self()->setCount(2); QCOMPARE(VirtualDesktopManager::self()->count(), 2u); VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::ForceTemporarily)); createTestWindow(); // The client should appear on the second virtual desktop. QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // Any attempt to move the client to another virtual desktop should fail. m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); // The rule should be discarded when the client is withdrawn. destroyTestWindow(); VirtualDesktopManager::self()->setCurrent(vd1); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); createTestWindow(); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); // One should be able to move the client between desktops. m_client->setDesktops({vd2}); QCOMPARE(m_client->desktops(), {vd2}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); m_client->setDesktops({vd1}); QCOMPARE(m_client->desktops(), {vd1}); QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeDontAffect() { setWindowRule("minimize", true, int(Rules::DontAffect)); createTestWindow(); QVERIFY(m_client->isMinimizable()); // The client should not be minimized. QVERIFY(!m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeApply() { setWindowRule("minimize", true, int(Rules::Apply)); createTestWindow(ClientShouldBeInactive); QVERIFY(m_client->isMinimizable()); // The client should be minimized. QVERIFY(m_client->isMinimized()); // We should still be able to unminimize the client. m_client->unminimize(); QVERIFY(!m_client->isMinimized()); // If we re-open the client, it should be minimized back again. destroyTestWindow(); createTestWindow(ClientShouldBeInactive); QVERIFY(m_client->isMinimizable()); QVERIFY(m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeRemember() { setWindowRule("minimize", false, int(Rules::Remember)); createTestWindow(); QVERIFY(m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); // Minimize the client. m_client->minimize(); QVERIFY(m_client->isMinimized()); // If we open the client again, it should be minimized. destroyTestWindow(); createTestWindow(ClientShouldBeInactive); QVERIFY(m_client->isMinimizable()); QVERIFY(m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeForce() { setWindowRule("minimize", false, int(Rules::Force)); createTestWindow(); QVERIFY(!m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); // Any attempt to minimize the client should fail. m_client->minimize(); QVERIFY(!m_client->isMinimized()); // If we re-open the client, the minimized state should still be forced. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); m_client->minimize(); QVERIFY(!m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeApplyNow() { createTestWindow(); QVERIFY(m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); setWindowRule("minimize", true, int(Rules::ApplyNow)); // The client should be minimized now. QVERIFY(m_client->isMinimizable()); QVERIFY(m_client->isMinimized()); // One is still able to unminimize the client. m_client->unminimize(); QVERIFY(!m_client->isMinimized()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testMinimizeForceTemporarily() { setWindowRule("minimize", false, int(Rules::ForceTemporarily)); createTestWindow(); QVERIFY(!m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); // Any attempt to minimize the client should fail until the client is closed. m_client->minimize(); QVERIFY(!m_client->isMinimized()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isMinimizable()); QVERIFY(!m_client->isMinimized()); m_client->minimize(); QVERIFY(m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarDontAffect() { setWindowRule("skiptaskbar", true, int(Rules::DontAffect)); createTestWindow(); // The client should not be affected by the rule. QVERIFY(!m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarApply() { setWindowRule("skiptaskbar", true, int(Rules::Apply)); createTestWindow(); // The client should not be included on a taskbar. QVERIFY(m_client->skipTaskbar()); // Though one can change that. m_client->setOriginalSkipTaskbar(false); QVERIFY(!m_client->skipTaskbar()); // Reopen the client, the rule should be applied again. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarRemember() { setWindowRule("skiptaskbar", true, int(Rules::Remember)); createTestWindow(); // The client should not be included on a taskbar. QVERIFY(m_client->skipTaskbar()); // Change the skip-taskbar state. m_client->setOriginalSkipTaskbar(false); QVERIFY(!m_client->skipTaskbar()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The client should be included on a taskbar. QVERIFY(!m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarForce() { setWindowRule("skiptaskbar", true, int(Rules::Force)); createTestWindow(); // The client should not be included on a taskbar. QVERIFY(m_client->skipTaskbar()); // Any attempt to change the skip-taskbar state should not succeed. m_client->setOriginalSkipTaskbar(false); QVERIFY(m_client->skipTaskbar()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The skip-taskbar state should be still forced. QVERIFY(m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarApplyNow() { createTestWindow(); QVERIFY(!m_client->skipTaskbar()); setWindowRule("skiptaskbar", true, int(Rules::ApplyNow)); // The client should not be on a taskbar now. QVERIFY(m_client->skipTaskbar()); // Also, one change the skip-taskbar state. m_client->setOriginalSkipTaskbar(false); QVERIFY(!m_client->skipTaskbar()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipTaskbarForceTemporarily() { setWindowRule("skiptaskbar", true, int(Rules::ForceTemporarily)); createTestWindow(); // The client should not be included on a taskbar. QVERIFY(m_client->skipTaskbar()); // Any attempt to change the skip-taskbar state should not succeed. m_client->setOriginalSkipTaskbar(false); QVERIFY(m_client->skipTaskbar()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->skipTaskbar()); // The skip-taskbar state is no longer forced. m_client->setOriginalSkipTaskbar(true); QVERIFY(m_client->skipTaskbar()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerDontAffect() { setWindowRule("skippager", true, int(Rules::DontAffect)); createTestWindow(); // The client should not be affected by the rule. QVERIFY(!m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerApply() { setWindowRule("skippager", true, int(Rules::Apply)); createTestWindow(); // The client should not be included on a pager. QVERIFY(m_client->skipPager()); // Though one can change that. m_client->setSkipPager(false); QVERIFY(!m_client->skipPager()); // Reopen the client, the rule should be applied again. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerRemember() { setWindowRule("skippager", true, int(Rules::Remember)); createTestWindow(); // The client should not be included on a pager. QVERIFY(m_client->skipPager()); // Change the skip-pager state. m_client->setSkipPager(false); QVERIFY(!m_client->skipPager()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The client should be included on a pager. QVERIFY(!m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerForce() { setWindowRule("skippager", true, int(Rules::Force)); createTestWindow(); // The client should not be included on a pager. QVERIFY(m_client->skipPager()); // Any attempt to change the skip-pager state should not succeed. m_client->setSkipPager(false); QVERIFY(m_client->skipPager()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The skip-pager state should be still forced. QVERIFY(m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerApplyNow() { createTestWindow(); QVERIFY(!m_client->skipPager()); setWindowRule("skippager", true, int(Rules::ApplyNow)); // The client should not be on a pager now. QVERIFY(m_client->skipPager()); // Also, one change the skip-pager state. m_client->setSkipPager(false); QVERIFY(!m_client->skipPager()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipPagerForceTemporarily() { setWindowRule("skippager", true, int(Rules::ForceTemporarily)); createTestWindow(); // The client should not be included on a pager. QVERIFY(m_client->skipPager()); // Any attempt to change the skip-pager state should not succeed. m_client->setSkipPager(false); QVERIFY(m_client->skipPager()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->skipPager()); // The skip-pager state is no longer forced. m_client->setSkipPager(true); QVERIFY(m_client->skipPager()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherDontAffect() { setWindowRule("skipswitcher", true, int(Rules::DontAffect)); createTestWindow(); // The client should not be affected by the rule. QVERIFY(!m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherApply() { setWindowRule("skipswitcher", true, int(Rules::Apply)); createTestWindow(); // The client should be excluded from window switching effects. QVERIFY(m_client->skipSwitcher()); // Though one can change that. m_client->setSkipSwitcher(false); QVERIFY(!m_client->skipSwitcher()); // Reopen the client, the rule should be applied again. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherRemember() { setWindowRule("skipswitcher", true, int(Rules::Remember)); createTestWindow(); // The client should be excluded from window switching effects. QVERIFY(m_client->skipSwitcher()); // Change the skip-switcher state. m_client->setSkipSwitcher(false); QVERIFY(!m_client->skipSwitcher()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The client should be included in window switching effects. QVERIFY(!m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherForce() { setWindowRule("skipswitcher", true, int(Rules::Force)); createTestWindow(); // The client should be excluded from window switching effects. QVERIFY(m_client->skipSwitcher()); // Any attempt to change the skip-switcher state should not succeed. m_client->setSkipSwitcher(false); QVERIFY(m_client->skipSwitcher()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The skip-switcher state should be still forced. QVERIFY(m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherApplyNow() { createTestWindow(); QVERIFY(!m_client->skipSwitcher()); setWindowRule("skipswitcher", true, int(Rules::ApplyNow)); // The client should be excluded from window switching effects now. QVERIFY(m_client->skipSwitcher()); // Also, one change the skip-switcher state. m_client->setSkipSwitcher(false); QVERIFY(!m_client->skipSwitcher()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testSkipSwitcherForceTemporarily() { setWindowRule("skipswitcher", true, int(Rules::ForceTemporarily)); createTestWindow(); // The client should be excluded from window switching effects. QVERIFY(m_client->skipSwitcher()); // Any attempt to change the skip-switcher state should not succeed. m_client->setSkipSwitcher(false); QVERIFY(m_client->skipSwitcher()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->skipSwitcher()); // The skip-switcher state is no longer forced. m_client->setSkipSwitcher(true); QVERIFY(m_client->skipSwitcher()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveDontAffect() { setWindowRule("above", true, int(Rules::DontAffect)); createTestWindow(); // The keep-above state of the client should not be affected by the rule. QVERIFY(!m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveApply() { setWindowRule("above", true, int(Rules::Apply)); createTestWindow(); // Initially, the client should be kept above. QVERIFY(m_client->keepAbove()); // One should also be able to alter the keep-above state. m_client->setKeepAbove(false); QVERIFY(!m_client->keepAbove()); // If one re-opens the client, it should be kept above back again. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveRemember() { setWindowRule("above", true, int(Rules::Remember)); createTestWindow(); // Initially, the client should be kept above. QVERIFY(m_client->keepAbove()); // Unset the keep-above state. m_client->setKeepAbove(false); QVERIFY(!m_client->keepAbove()); destroyTestWindow(); // Re-open the client, it should not be kept above. createTestWindow(); QVERIFY(!m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveForce() { setWindowRule("above", true, int(Rules::Force)); createTestWindow(); // Initially, the client should be kept above. QVERIFY(m_client->keepAbove()); // Any attemt to unset the keep-above should not succeed. m_client->setKeepAbove(false); QVERIFY(m_client->keepAbove()); // If we re-open the client, it should still be kept above. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveApplyNow() { createTestWindow(); QVERIFY(!m_client->keepAbove()); setWindowRule("above", true, int(Rules::ApplyNow)); // The client should now be kept above other clients. QVERIFY(m_client->keepAbove()); // One is still able to change the keep-above state of the client. m_client->setKeepAbove(false); QVERIFY(!m_client->keepAbove()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepAboveForceTemporarily() { setWindowRule("above", true, int(Rules::ForceTemporarily)); createTestWindow(); // Initially, the client should be kept above. QVERIFY(m_client->keepAbove()); // Any attempt to alter the keep-above state should not succeed. m_client->setKeepAbove(false); QVERIFY(m_client->keepAbove()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->keepAbove()); // The keep-above state is no longer forced. m_client->setKeepAbove(true); QVERIFY(m_client->keepAbove()); m_client->setKeepAbove(false); QVERIFY(!m_client->keepAbove()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowDontAffect() { setWindowRule("below", true, int(Rules::DontAffect)); createTestWindow(); // The keep-below state of the client should not be affected by the rule. QVERIFY(!m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowApply() { setWindowRule("below", true, int(Rules::Apply)); createTestWindow(); // Initially, the client should be kept below. QVERIFY(m_client->keepBelow()); // One should also be able to alter the keep-below state. m_client->setKeepBelow(false); QVERIFY(!m_client->keepBelow()); // If one re-opens the client, it should be kept above back again. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowRemember() { setWindowRule("below", true, int(Rules::Remember)); createTestWindow(); // Initially, the client should be kept below. QVERIFY(m_client->keepBelow()); // Unset the keep-below state. m_client->setKeepBelow(false); QVERIFY(!m_client->keepBelow()); destroyTestWindow(); // Re-open the client, it should not be kept below. createTestWindow(); QVERIFY(!m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowForce() { setWindowRule("below", true, int(Rules::Force)); createTestWindow(); // Initially, the client should be kept below. QVERIFY(m_client->keepBelow()); // Any attemt to unset the keep-below should not succeed. m_client->setKeepBelow(false); QVERIFY(m_client->keepBelow()); // If we re-open the client, it should still be kept below. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowApplyNow() { createTestWindow(); QVERIFY(!m_client->keepBelow()); setWindowRule("below", true, int(Rules::ApplyNow)); // The client should now be kept below other clients. QVERIFY(m_client->keepBelow()); // One is still able to change the keep-below state of the client. m_client->setKeepBelow(false); QVERIFY(!m_client->keepBelow()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testKeepBelowForceTemporarily() { setWindowRule("below", true, int(Rules::ForceTemporarily)); createTestWindow(); // Initially, the client should be kept below. QVERIFY(m_client->keepBelow()); // Any attempt to alter the keep-below state should not succeed. m_client->setKeepBelow(false); QVERIFY(m_client->keepBelow()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(!m_client->keepBelow()); // The keep-below state is no longer forced. m_client->setKeepBelow(true); QVERIFY(m_client->keepBelow()); m_client->setKeepBelow(false); QVERIFY(!m_client->keepBelow()); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutDontAffect() { setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::DontAffect)); createTestWindow(); QCOMPARE(m_client->shortcut(), QKeySequence()); m_client->minimize(); QVERIFY(m_client->isMinimized()); // If we press the window shortcut, nothing should happen. QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(m_client->isMinimized()); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutApply() { setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Apply)); createTestWindow(); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // One can also change the shortcut. m_client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // The old shortcut should do nothing. m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(m_client->isMinimized()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The window shortcut should be set back to Ctrl+Alt+1. QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutRemember() { QSKIP("KWin core doesn't try to save the last used window shortcut"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Remember)); createTestWindow(); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // Change the window shortcut to Ctrl+Alt+2. m_client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The window shortcut should be set to the last known value. QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutForce() { QSKIP("KWin core can't release forced window shortcuts"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Force)); createTestWindow(); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // Any attempt to change the window shortcut should not succeed. m_client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(m_client->isMinimized()); // Reopen the client. destroyTestWindow(); createTestWindow(); // The window shortcut should still be forced. QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutApplyNow() { createTestWindow(); QVERIFY(m_client->shortcut().isEmpty()); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ApplyNow)); // The client should now have a window shortcut assigned. QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // Assign a different shortcut. m_client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // The rule should not be applied again. m_client->evaluateWindowRules(); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); destroyTestWindow(); } void TestXdgShellClientRules::testShortcutForceTemporarily() { QSKIP("KWin core can't release forced window shortcuts"); setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ForceTemporarily)); createTestWindow(); // If we press the window shortcut, the window should be brought back to user. QSignalSpy clientUnminimizedSpy(m_client, &AbstractClient::clientUnminimized); QVERIFY(clientUnminimizedSpy.isValid()); quint32 timestamp = 1; QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_1, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(clientUnminimizedSpy.wait()); QVERIFY(!m_client->isMinimized()); // Any attempt to change the window shortcut should not succeed. m_client->setShortcut(QStringLiteral("Ctrl+Alt+2")); QCOMPARE(m_client->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); m_client->minimize(); QVERIFY(m_client->isMinimized()); Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); Test::keyboardKeyPressed(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_2, timestamp++); Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); QVERIFY(!clientUnminimizedSpy.wait(100)); QVERIFY(m_client->isMinimized()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->shortcut().isEmpty()); destroyTestWindow(); } 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)); createTestWindow(); QVERIFY(m_client->isActive()); // The opacity should not be affected by the rule. QCOMPARE(m_client->opacity(), 1.0); destroyTestWindow(); } void TestXdgShellClientRules::testActiveOpacityForce() { setWindowRule("opacityactive", 90, int(Rules::Force)); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 0.9); destroyTestWindow(); } void TestXdgShellClientRules::testActiveOpacityForceTemporarily() { setWindowRule("opacityactive", 90, int(Rules::ForceTemporarily)); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 0.9); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 1.0); destroyTestWindow(); } void TestXdgShellClientRules::testInactiveOpacityDontAffect() { setWindowRule("opacityinactive", 80, int(Rules::DontAffect)); createTestWindow(); QVERIFY(m_client->isActive()); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!m_client->isActive()); // The opacity of the client should not be affected by the rule. QCOMPARE(m_client->opacity(), 1.0); destroyTestWindow(); } void TestXdgShellClientRules::testInactiveOpacityForce() { setWindowRule("opacityinactive", 80, int(Rules::Force)); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 1.0); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!m_client->isActive()); // The opacity should be forced by the rule. QCOMPARE(m_client->opacity(), 0.8); destroyTestWindow(); } void TestXdgShellClientRules::testInactiveOpacityForceTemporarily() { setWindowRule("opacityinactive", 80, int(Rules::ForceTemporarily)); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 1.0); // Make the client inactive. workspace()->setActiveClient(nullptr); QVERIFY(!m_client->isActive()); // The opacity should be forced by the rule. QCOMPARE(m_client->opacity(), 0.8); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(); QVERIFY(m_client->isActive()); QCOMPARE(m_client->opacity(), 1.0); workspace()->setActiveClient(nullptr); QVERIFY(!m_client->isActive()); QCOMPARE(m_client->opacity(), 1.0); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderDontAffect() { setWindowRule("noborder", true, int(Rules::DontAffect)); createTestWindow(ServerSideDecoration); // The client should not be affected by the rule. QVERIFY(!m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderApply() { setWindowRule("noborder", true, int(Rules::Apply)); createTestWindow(ServerSideDecoration); // Initially, the client should not be decorated. QVERIFY(m_client->noBorder()); QVERIFY(!m_client->isDecorated()); // But you should be able to change "no border" property afterwards. QVERIFY(m_client->userCanSetNoBorder()); m_client->setNoBorder(false); QVERIFY(!m_client->noBorder()); // If one re-opens the client, it should have no border again. destroyTestWindow(); createTestWindow(ServerSideDecoration); QVERIFY(m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderRemember() { setWindowRule("noborder", true, int(Rules::Remember)); createTestWindow(ServerSideDecoration); // Initially, the client should not be decorated. QVERIFY(m_client->noBorder()); QVERIFY(!m_client->isDecorated()); // Unset the "no border" property. QVERIFY(m_client->userCanSetNoBorder()); m_client->setNoBorder(false); QVERIFY(!m_client->noBorder()); // Re-open the client, it should be decorated. destroyTestWindow(); createTestWindow(ServerSideDecoration); QVERIFY(m_client->isDecorated()); QVERIFY(!m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderForce() { setWindowRule("noborder", true, int(Rules::Force)); createTestWindow(ServerSideDecoration); // The client should not be decorated. QVERIFY(m_client->noBorder()); QVERIFY(!m_client->isDecorated()); // And the user should not be able to change the "no border" property. m_client->setNoBorder(false); QVERIFY(m_client->noBorder()); // Reopen the client. destroyTestWindow(); createTestWindow(ServerSideDecoration); // The "no border" property should be still forced. QVERIFY(m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderApplyNow() { createTestWindow(ServerSideDecoration); QVERIFY(!m_client->noBorder()); // Initialize RuleBook with the test rule. setWindowRule("noborder", true, int(Rules::ApplyNow)); // The "no border" property should be set now. QVERIFY(m_client->noBorder()); // One should be still able to change the "no border" property. m_client->setNoBorder(false); QVERIFY(!m_client->noBorder()); // The rule should not be applied again. m_client->evaluateWindowRules(); QVERIFY(!m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testNoBorderForceTemporarily() { setWindowRule("noborder", true, int(Rules::ForceTemporarily)); createTestWindow(ServerSideDecoration); // The "no border" property should be set. QVERIFY(m_client->noBorder()); // And you should not be able to change it. m_client->setNoBorder(false); QVERIFY(m_client->noBorder()); // The rule should be discarded when the client is closed. destroyTestWindow(); createTestWindow(ServerSideDecoration); QVERIFY(!m_client->noBorder()); // The "no border" property is no longer forced. m_client->setNoBorder(true); QVERIFY(m_client->noBorder()); m_client->setNoBorder(false); QVERIFY(!m_client->noBorder()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenDontAffect() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::DontAffect)); createTestWindow(); // The client should not be affected by the rule. QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); // The user can still move the client to another screen. workspace()->sendClientToOutput(m_client, outputs.at(1)); QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenApply() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::Apply)); createTestWindow(); // 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(m_client->output()->name(), outputs.at(1)->name()); // The user can move the client to another screen. workspace()->sendClientToOutput(m_client, outputs.at(0)); QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenRemember() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); setWindowRule("screen", int(1), int(Rules::Remember)); createTestWindow(); // Initially, the client should be in the first screen QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); // Move the client to the second screen. workspace()->sendClientToOutput(m_client, outputs.at(1)); QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. destroyTestWindow(); createTestWindow(); QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenForce() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); createTestWindow(); QVERIFY(m_client->isActive()); setWindowRule("screen", int(1), int(Rules::Force)); // The client should be forced to the screen specified by the rule. QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); // User should not be able to move the client to another screen. workspace()->sendClientToOutput(m_client, outputs.at(0)); QCOMPARE(m_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(m_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(m_client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. destroyTestWindow(); createTestWindow(); QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenApplyNow() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); createTestWindow(); QCOMPARE(m_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(m_client->output()->name(), outputs.at(1)->name()); // The user can move the client to another screen. workspace()->sendClientToOutput(m_client, outputs.at(0)); QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); // The rule should not be applied again. m_client->evaluateWindowRules(); QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); destroyTestWindow(); } void TestXdgShellClientRules::testScreenForceTemporarily() { const KWin::Outputs outputs = kwinApp()->platform()->enabledOutputs(); createTestWindow(); setWindowRule("screen", int(1), int(Rules::ForceTemporarily)); // The client should be forced the second screen QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); // User is not allowed to move it workspace()->sendClientToOutput(m_client, outputs.at(0)); QCOMPARE(m_client->output()->name(), outputs.at(1)->name()); // Close and reopen the client. destroyTestWindow(); createTestWindow(); // The rule should be discarded now QCOMPARE(m_client->output()->name(), outputs.at(0)->name()); destroyTestWindow(); } 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"