kwin/autotests/integration/xdgshellclient_rules_test.cpp
Ismael Asensio 75d71a1c1a AbstractClient: Re-check screen rules on screen enabled
BUG: 409979
FIXED-IN: 5.25
2022-04-16 16:02:12 +00:00

3005 lines
107 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2022 Ismael Asensio <isma.af@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kwin_wayland_test.h"
#include "abstract_client.h"
#include "cursor.h"
#include "output.h"
#include "outputconfiguration.h"
#include "platform.h"
#include "rules.h"
#include "virtualdesktops.h"
#include "wayland_server.h"
#include "workspace.h"
#include <KWayland/Client/surface.h>
#include <linux/input.h>
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<typename T>
void setWindowRule(const QString &property, const T &value, int policy);
private:
KSharedConfig::Ptr m_config;
AbstractClient *m_client;
QScopedPointer<KWayland::Client::Surface> m_surface;
QScopedPointer<Test::XdgToplevel> m_shellSurface;
QScopedPointer<QSignalSpy> m_toplevelConfigureRequestedSpy;
QScopedPointer<QSignalSpy> m_surfaceConfigureRequestedSpy;
};
void TestXdgShellClientRules::initTestCase()
{
qRegisterMetaType<KWin::AbstractClient *>();
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<quint32>());
// 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<typename T>
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<quint32>());
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<quint32>());
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<quint32>());
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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<Test::XdgToplevel::States>();
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
OutputConfiguration config;
auto changeSet = config.changeSet(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());
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<KWayland::Client::Surface> surface(Test::createSurface());
QScopedPointer<Test::XdgToplevel> 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"