kwin/autotests/test_screen_edges.cpp
David Edmundson 04ab8554aa Add hint similar to autohide to raise/lower a window
Add action type to screen edge show to allow raise/lower as well as
autohide

Add an action type to screen edge show to allow raise/lower as well as
autohide. This uses the same atom, using a mask to separate type and
location.

The logic for handling geometry changes is moved from the screenedge to
the client so that we can handle both types without screenedge needing
to know what the raise is for.

REVIEW: 124272
2015-10-12 10:37:16 +01:00

862 lines
29 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2014 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
// kwin
#include "../atoms.h"
#include "../cursor.h"
#include "../input.h"
#include "../main.h"
#include "../screenedge.h"
#include "../screens.h"
#include "../utils.h"
#include "../virtualdesktops.h"
#include "../xcbutils.h"
#include "mock_client.h"
#include "mock_screens.h"
#include "mock_workspace.h"
// Frameworks
#include <KConfigGroup>
// Qt
#include <QtTest/QtTest>
// xcb
#include <xcb/xcb.h>
Q_DECLARE_METATYPE(KWin::ElectricBorder)
Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core")
namespace KWin
{
Atoms* atoms;
int screen_number = 0;
Cursor *Cursor::s_self = nullptr;
static QPoint s_cursorPos = QPoint();
QPoint Cursor::pos()
{
return s_cursorPos;
}
void Cursor::setPos(const QPoint &pos)
{
s_cursorPos = pos;
}
void Cursor::setPos(int x, int y)
{
setPos(QPoint(x, y));
}
void Cursor::startMousePolling()
{
}
void Cursor::stopMousePolling()
{
}
InputRedirection *InputRedirection::s_self = nullptr;
void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action)
{
Q_UNUSED(shortcut)
Q_UNUSED(action)
}
void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action)
{
Q_UNUSED(modifiers)
Q_UNUSED(axis)
Q_UNUSED(action)
}
void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action)
{
Q_UNUSED(action)
}
void updateXTime()
{
}
Application::OperationMode Application::operationMode() const
{
return OperationModeX11;
}
class TestObject : public QObject
{
Q_OBJECT
public Q_SLOTS:
bool callback(ElectricBorder border);
Q_SIGNALS:
void gotCallback(KWin::ElectricBorder);
};
bool TestObject::callback(KWin::ElectricBorder border)
{
emit gotCallback(border);
return true;
}
}
class TestScreenEdges : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
void testInit();
void testCreatingInitialEdges();
void testCallback();
void testCallbackWithCheck();
void testPushBack_data();
void testPushBack();
void testFullScreenBlocking();
void testClientEdge();
};
void TestScreenEdges::initTestCase()
{
qApp->setProperty("x11RootWindow", QVariant::fromValue<quint32>(QX11Info::appRootWindow()));
qApp->setProperty("x11Connection", QVariant::fromValue<void*>(QX11Info::connection()));
KWin::atoms = new KWin::Atoms;
qRegisterMetaType<KWin::ElectricBorder>();
}
void TestScreenEdges::cleanupTestCase()
{
delete KWin::atoms;
}
void TestScreenEdges::init()
{
using namespace KWin;
new MockWorkspace;
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
Screens::create();
auto vd = VirtualDesktopManager::create();
vd->setConfig(config);
vd->load();
auto s = ScreenEdges::create();
s->setConfig(config);
}
void TestScreenEdges::cleanup()
{
using namespace KWin;
delete ScreenEdges::self();
delete VirtualDesktopManager::self();
delete Screens::self();
delete workspace();
}
void TestScreenEdges::testInit()
{
using namespace KWin;
auto s = ScreenEdges::self();
s->init();
QCOMPARE(s->isDesktopSwitching(), false);
QCOMPARE(s->isDesktopSwitchingMovingClients(), false);
QCOMPARE(s->timeThreshold(), 150);
QCOMPARE(s->reActivationThreshold(), 350);
QCOMPARE(s->cursorPushBackDistance(), QSize(1, 1));
QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone);
QList<Edge*> edges = s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly);
QCOMPARE(edges.size(), 8);
for (auto e : edges) {
QVERIFY(!e->isReserved());
QVERIFY(e->inherits("KWin::WindowBasedEdge"));
QVERIFY(!e->inherits("KWin::AreaBasedEdge"));
QVERIFY(!e->client());
QVERIFY(!e->isApproaching());
}
Edge *te = edges.at(0);
QVERIFY(te->isCorner());
QVERIFY(!te->isScreenEdge());
QVERIFY(te->isLeft());
QVERIFY(te->isTop());
QVERIFY(!te->isRight());
QVERIFY(!te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricTopLeft);
te = edges.at(1);
QVERIFY(te->isCorner());
QVERIFY(!te->isScreenEdge());
QVERIFY(te->isLeft());
QVERIFY(!te->isTop());
QVERIFY(!te->isRight());
QVERIFY(te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricBottomLeft);
te = edges.at(2);
QVERIFY(!te->isCorner());
QVERIFY(te->isScreenEdge());
QVERIFY(te->isLeft());
QVERIFY(!te->isTop());
QVERIFY(!te->isRight());
QVERIFY(!te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricLeft);
te = edges.at(3);
QVERIFY(te->isCorner());
QVERIFY(!te->isScreenEdge());
QVERIFY(!te->isLeft());
QVERIFY(te->isTop());
QVERIFY(te->isRight());
QVERIFY(!te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricTopRight);
te = edges.at(4);
QVERIFY(te->isCorner());
QVERIFY(!te->isScreenEdge());
QVERIFY(!te->isLeft());
QVERIFY(!te->isTop());
QVERIFY(te->isRight());
QVERIFY(te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricBottomRight);
te = edges.at(5);
QVERIFY(!te->isCorner());
QVERIFY(te->isScreenEdge());
QVERIFY(!te->isLeft());
QVERIFY(!te->isTop());
QVERIFY(te->isRight());
QVERIFY(!te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricRight);
te = edges.at(6);
QVERIFY(!te->isCorner());
QVERIFY(te->isScreenEdge());
QVERIFY(!te->isLeft());
QVERIFY(te->isTop());
QVERIFY(!te->isRight());
QVERIFY(!te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricTop);
te = edges.at(7);
QVERIFY(!te->isCorner());
QVERIFY(te->isScreenEdge());
QVERIFY(!te->isLeft());
QVERIFY(!te->isTop());
QVERIFY(!te->isRight());
QVERIFY(te->isBottom());
QCOMPARE(te->border(), ElectricBorder::ElectricBottom);
// we shouldn't have any x windows, though
QCOMPARE(s->windows().size(), 0);
}
void TestScreenEdges::testCreatingInitialEdges()
{
using namespace KWin;
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("Windows").writeEntry("ElectricBorders", 2/*ElectricAlways*/);
config->sync();
auto s = ScreenEdges::self();
s->setConfig(config);
s->init();
// we don't have multiple desktops, so it's returning false
QCOMPARE(s->isDesktopSwitching(), true);
QCOMPARE(s->isDesktopSwitchingMovingClients(), true);
QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone);
QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone);
QEXPECT_FAIL("", "needs fixing", Continue);
QCOMPARE(s->windows().size(), 0);
// set some reasonable virtual desktops
config->group("Desktops").writeEntry("Number", 4);
config->sync();
auto vd = VirtualDesktopManager::self();
vd->setConfig(config);
vd->load();
QCOMPARE(vd->count(), 4u);
QCOMPARE(vd->grid().width(), 2);
QCOMPARE(vd->grid().height(), 2);
// approach windows for edges not created as screen too small
s->updateLayout();
auto edgeWindows = s->windows();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
if (!Xcb::Extensions::self()->isRandrAvailable()) {
QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort);
}
#endif
QCOMPARE(edgeWindows.size(), 12);
auto testWindowGeometry = [&](int index) {
Xcb::WindowGeometry geo(edgeWindows[index]);
return geo.rect();
};
QRect sg = screens()->geometry();
const int co = s->cornerOffset();
QList<QRect> expectedGeometries{
QRect(0, 0, 1, 1),
QRect(0, 0, co, co),
QRect(0, sg.bottom(), 1, 1),
QRect(0, sg.height() - co, co, co),
QRect(0, co, 1, sg.height() - co*2),
// QRect(0, co * 2 + 1, co, sg.height() - co*4),
QRect(sg.right(), 0, 1, 1),
QRect(sg.right() - co + 1, 0, co, co),
QRect(sg.right(), sg.bottom(), 1, 1),
QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co),
QRect(sg.right(), co, 1, sg.height() - co*2),
// QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4),
QRect(co, 0, sg.width() - co * 2, 1),
// QRect(co * 2, 0, sg.width() - co * 4, co),
QRect(co, sg.bottom(), sg.width() - co * 2, 1),
// QRect(co * 2, sg.height() - co, sg.width() - co * 4, co)
};
for (int i = 0; i < 12; ++i) {
QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i));
}
QList<Edge*> edges = s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly);
QCOMPARE(edges.size(), 8);
for (auto e : edges) {
QVERIFY(e->isReserved());
}
static_cast<MockScreens*>(screens())->setGeometries(QList<QRect>{QRect{0, 0, 1024, 768}});
QSignalSpy changedSpy(screens(), SIGNAL(changed()));
QVERIFY(changedSpy.isValid());
// first is before it's updated
QVERIFY(changedSpy.wait());
// second is after it's updated
QVERIFY(changedSpy.wait());
// let's update the layout and verify that we have edges
s->recreateEdges();
edgeWindows = s->windows();
QCOMPARE(edgeWindows.size(), 16);
sg = screens()->geometry();
expectedGeometries = QList<QRect>{
QRect(0, 0, 1, 1),
QRect(0, 0, co, co),
QRect(0, sg.bottom(), 1, 1),
QRect(0, sg.height() - co, co, co),
QRect(0, co, 1, sg.height() - co*2),
QRect(0, co * 2 + 1, co, sg.height() - co*4),
QRect(sg.right(), 0, 1, 1),
QRect(sg.right() - co + 1, 0, co, co),
QRect(sg.right(), sg.bottom(), 1, 1),
QRect(sg.right() - co + 1, sg.bottom() - co + 1, co, co),
QRect(sg.right(), co, 1, sg.height() - co*2),
QRect(sg.right() - co + 1, co * 2, co, sg.height() - co*4),
QRect(co, 0, sg.width() - co * 2, 1),
QRect(co * 2, 0, sg.width() - co * 4, co),
QRect(co, sg.bottom(), sg.width() - co * 2, 1),
QRect(co * 2, sg.height() - co, sg.width() - co * 4, co)
};
for (int i = 0; i < 16; ++i) {
QCOMPARE(testWindowGeometry(i), expectedGeometries.at(i));
}
// disable desktop switching again
config->group("Windows").writeEntry("ElectricBorders", 1/*ElectricMoveOnly*/);
s->reconfigure();
QCOMPARE(s->isDesktopSwitching(), false);
QCOMPARE(s->isDesktopSwitchingMovingClients(), true);
QCOMPARE(s->windows().size(), 0);
edges = s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly);
QCOMPARE(edges.size(), 8);
for (int i = 0; i < 8; ++i) {
auto e = edges.at(i);
QVERIFY(!e->isReserved());
QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1));
}
}
void TestScreenEdges::testCallback()
{
using namespace KWin;
MockWorkspace ws;
static_cast<MockScreens*>(screens())->setGeometries(QList<QRect>{QRect{0, 0, 1024, 768}, QRect{200, 768, 1024, 768}});
QSignalSpy changedSpy(screens(), SIGNAL(changed()));
QVERIFY(changedSpy.isValid());
// first is before it's updated
QVERIFY(changedSpy.wait());
// second is after it's updated
QVERIFY(changedSpy.wait());
auto s = ScreenEdges::self();
s->init();
TestObject callback;
QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder)));
QVERIFY(spy.isValid());
s->reserve(ElectricLeft, &callback, "callback");
s->reserve(ElectricTopLeft, &callback, "callback");
s->reserve(ElectricTop, &callback, "callback");
s->reserve(ElectricTopRight, &callback, "callback");
s->reserve(ElectricRight, &callback, "callback");
s->reserve(ElectricBottomRight, &callback, "callback");
s->reserve(ElectricBottom, &callback, "callback");
s->reserve(ElectricBottomLeft, &callback, "callback");
QList<Edge*> edges = s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly);
QCOMPARE(edges.size(), 10);
for (auto e: edges) {
QVERIFY(e->isReserved());
}
auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) {
return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768;
});
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
if (!Xcb::Extensions::self()->isRandrAvailable()) {
QEXPECT_FAIL("", "Broken on no xrandr systems in Qt 5.5", Abort);
}
#endif
QVERIFY(it != edges.constEnd());
xcb_enter_notify_event_t event;
auto setPos = [&event] (const QPoint &pos) {
Cursor::setPos(pos);
event.root_x = pos.x();
event.root_y = pos.y();
event.event_x = pos.x();
event.event_y = pos.y();
};
event.root = XCB_WINDOW_NONE;
event.child = XCB_WINDOW_NONE;
event.event = static_cast<WindowBasedEdge*>(*it)->window();
event.same_screen_focus = 1;
event.time = QDateTime::currentMSecsSinceEpoch();
setPos(QPoint(0, 50));
QVERIFY(s->isEntered(&event));
// doesn't trigger as the edge was not triggered yet
QVERIFY(spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 50));
// test doesn't trigger due to too much offset
QTest::qWait(160);
setPos(QPoint(0, 100));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 100));
// doesn't trigger as we are waiting too long already
QTest::qWait(200);
setPos(QPoint(0, 101));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 101));
// doesn't activate as we are waiting too short
QTest::qWait(50);
setPos(QPoint(0, 100));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 100));
// and this one triggers
QTest::qWait(110);
setPos(QPoint(0, 101));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(!spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 101));
// now let's try to trigger again
QTest::qWait(351);
setPos(QPoint(0, 100));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QCOMPARE(spy.count(), 1);
QCOMPARE(Cursor::pos(), QPoint(1, 100));
// it's still under the reactivation
QTest::qWait(50);
setPos(QPoint(0, 100));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QCOMPARE(spy.count(), 1);
QCOMPARE(Cursor::pos(), QPoint(1, 100));
// now it should trigger again
QTest::qWait(250);
setPos(QPoint(0, 100));
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QCOMPARE(spy.count(), 2);
QCOMPARE(spy.first().first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(spy.last().first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(Cursor::pos(), QPoint(1, 100));
// let's disable pushback
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 0);
config->sync();
s->setConfig(config);
s->reconfigure();
// it should trigger directly
QTest::qWait(350);
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QCOMPARE(spy.count(), 3);
QCOMPARE(spy.at(0).first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(spy.at(1).first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(spy.at(2).first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(Cursor::pos(), QPoint(0, 100));
// now let's unreserve again
s->unreserve(ElectricTopLeft, &callback);
s->unreserve(ElectricTop, &callback);
s->unreserve(ElectricTopRight, &callback);
s->unreserve(ElectricRight, &callback);
s->unreserve(ElectricBottomRight, &callback);
s->unreserve(ElectricBottom, &callback);
s->unreserve(ElectricBottomLeft, &callback);
s->unreserve(ElectricLeft, &callback);
for (auto e: s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly)) {
QVERIFY(!e->isReserved());
}
}
void TestScreenEdges::testCallbackWithCheck()
{
using namespace KWin;
auto s = ScreenEdges::self();
s->init();
TestObject callback;
QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder)));
QVERIFY(spy.isValid());
s->reserve(ElectricLeft, &callback, "callback");
// check activating a different edge doesn't do anything
s->check(QPoint(50, 0), QDateTime::currentDateTime(), true);
QVERIFY(spy.isEmpty());
// try a direct activate without pushback
Cursor::setPos(0, 50);
s->check(QPoint(0, 50), QDateTime::currentDateTime(), true);
QCOMPARE(spy.count(), 1);
QEXPECT_FAIL("", "Argument says force no pushback, but it gets pushed back. Needs investigation", Continue);
QCOMPARE(Cursor::pos(), QPoint(0, 50));
// use a different edge, this time with pushback
s->reserve(KWin::ElectricRight, &callback, "callback");
Cursor::setPos(99, 50);
s->check(QPoint(99, 50), QDateTime::currentDateTime());
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.last().first().value<ElectricBorder>(), ElectricLeft);
QCOMPARE(Cursor::pos(), QPoint(98, 50));
// and trigger it again
QTest::qWait(160);
Cursor::setPos(99, 50);
s->check(QPoint(99, 50), QDateTime::currentDateTime());
QCOMPARE(spy.count(), 2);
QCOMPARE(spy.last().first().value<ElectricBorder>(), ElectricRight);
QCOMPARE(Cursor::pos(), QPoint(98, 50));
}
void TestScreenEdges::testPushBack_data()
{
QTest::addColumn<KWin::ElectricBorder>("border");
QTest::addColumn<int>("pushback");
QTest::addColumn<QPoint>("trigger");
QTest::addColumn<QPoint>("expected");
QTest::newRow("topleft-3") << KWin::ElectricTopLeft << 3 << QPoint(0, 0) << QPoint(3, 3);
QTest::newRow("top-5") << KWin::ElectricTop << 5 << QPoint(50, 0) << QPoint(50, 5);
QTest::newRow("toprigth-2") << KWin::ElectricTopRight << 2 << QPoint(99, 0) << QPoint(97, 2);
QTest::newRow("right-10") << KWin::ElectricRight << 10 << QPoint(99, 50) << QPoint(89, 50);
QTest::newRow("bottomright-5") << KWin::ElectricBottomRight << 5 << QPoint(99, 99) << QPoint(94, 94);
QTest::newRow("bottom-10") << KWin::ElectricBottom << 10 << QPoint(50, 99) << QPoint(50, 89);
QTest::newRow("bottomleft-3") << KWin::ElectricBottomLeft << 3 << QPoint(0, 99) << QPoint(3, 96);
QTest::newRow("left-10") << KWin::ElectricLeft << 10 << QPoint(0, 50) << QPoint(10, 50);
QTest::newRow("invalid") << KWin::ElectricLeft << 10 << QPoint(50, 0) << QPoint(50, 0);
}
void TestScreenEdges::testPushBack()
{
using namespace KWin;
QFETCH(int, pushback);
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("Windows").writeEntry("ElectricBorderPushbackPixels", pushback);
config->sync();
// TODO: add screens
auto s = ScreenEdges::self();
s->setConfig(config);
s->init();
TestObject callback;
QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder)));
QVERIFY(spy.isValid());
QFETCH(ElectricBorder, border);
s->reserve(border, &callback, "callback");
QFETCH(QPoint, trigger);
Cursor::setPos(trigger);
xcb_enter_notify_event_t event;
event.root_x = trigger.x();
event.root_y = trigger.y();
event.event_x = trigger.x();
event.event_y = trigger.y();
event.root = XCB_WINDOW_NONE;
event.child = XCB_WINDOW_NONE;
event.event = s->windows().first();
event.same_screen_focus = 1;
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
QTEST(Cursor::pos(), "expected");
// do the same without the event, but the check method
Cursor::setPos(trigger);
s->check(trigger, QDateTime::currentDateTime());
QVERIFY(spy.isEmpty());
QTEST(Cursor::pos(), "expected");
}
void TestScreenEdges::testFullScreenBlocking()
{
using namespace KWin;
MockWorkspace ws;
Client client(&ws);
auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 1);
config->sync();
auto s = ScreenEdges::self();
s->setConfig(config);
s->init();
TestObject callback;
QSignalSpy spy(&callback, SIGNAL(gotCallback(KWin::ElectricBorder)));
QVERIFY(spy.isValid());
s->reserve(KWin::ElectricLeft, &callback, "callback");
s->reserve(KWin::ElectricBottomRight, &callback, "callback");
// currently there is no active client yet, so check blocking shouldn't do anything
emit s->checkBlocking();
xcb_enter_notify_event_t event;
Cursor::setPos(0, 50);
event.root_x = 0;
event.root_y = 50;
event.event_x = 0;
event.event_y = 50;
event.root = XCB_WINDOW_NONE;
event.child = XCB_WINDOW_NONE;
event.event = s->windows().first();
event.same_screen_focus = 1;
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 50));
client.setGeometry(screens()->geometry());
client.setActive(true);
client.setFullScreen(true);
ws.setActiveClient(&client);
emit s->checkBlocking();
// the signal doesn't trigger for corners, let's go over all windows just to be sure that it doesn't call for corners
for (auto e: s->findChildren<Edge*>()) {
e->checkBlocking();
}
// calling again should not trigger
QTest::qWait(160);
Cursor::setPos(0, 50);
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
// and no pushback
QCOMPARE(Cursor::pos(), QPoint(0, 50));
// let's make the client not fullscreen, which should trigger
client.setFullScreen(false);
emit s->checkBlocking();
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(!spy.isEmpty());
QCOMPARE(Cursor::pos(), QPoint(1, 50));
// let's make the client fullscreen again, but with a geometry not intersecting the left edge
QTest::qWait(351);
client.setFullScreen(true);
client.setGeometry(client.geometry().translated(10, 0));
emit s->checkBlocking();
spy.clear();
Cursor::setPos(0, 50);
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
// and a pushback
QCOMPARE(Cursor::pos(), QPoint(1, 50));
// just to be sure, let's set geometry back
client.setGeometry(screens()->geometry());
emit s->checkBlocking();
Cursor::setPos(0, 50);
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
// and no pushback
QCOMPARE(Cursor::pos(), QPoint(0, 50));
// the corner should always trigger
s->unreserve(KWin::ElectricLeft, &callback);
event.event_x = 99;
event.event_y = 99;
event.root_x = 99;
event.root_y = 99;
event.event = s->windows().first();
event.time = QDateTime::currentMSecsSinceEpoch();
Cursor::setPos(99, 99);
QVERIFY(s->isEntered(&event));
QVERIFY(spy.isEmpty());
// and pushback
QCOMPARE(Cursor::pos(), QPoint(98, 98));
QTest::qWait(160);
event.time = QDateTime::currentMSecsSinceEpoch();
Cursor::setPos(99, 99);
QVERIFY(s->isEntered(&event));
QVERIFY(!spy.isEmpty());
}
void TestScreenEdges::testClientEdge()
{
using namespace KWin;
Client client(workspace());
client.setGeometry(QRect(10, 50, 10, 50));
auto s = ScreenEdges::self();
s->init();
s->reserve(&client, KWin::ElectricBottom);
// let's set the client to be hidden
client.setHiddenInternal(true);
QPointer<Edge> edge = s->findChildren<Edge*>().last();
s->reserve(&client, KWin::ElectricBottom);
QCOMPARE(edge.data(), s->findChildren<Edge*>().last());
QCOMPARE(edge->isReserved(), true);
// for none of the edges it should be able to be set
for (int i = 0; i < ELECTRIC_COUNT; ++i) {
client.setHiddenInternal(true);
s->reserve(&client, static_cast<ElectricBorder>(i));
QCOMPARE(client.isHiddenInternal(), false);
}
// now let's try to set it and activate it
client.setGeometry(screens()->geometry());
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricLeft);
QCOMPARE(client.isHiddenInternal(), true);
xcb_enter_notify_event_t event;
Cursor::setPos(0, 50);
event.root_x = 0;
event.root_y = 50;
event.event_x = 0;
event.event_y = 50;
event.root = XCB_WINDOW_NONE;
event.child = XCB_WINDOW_NONE;
event.event = s->windows().first();
event.same_screen_focus = 1;
event.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event));
// autohiding panels shall activate instantly
QCOMPARE(client.isHiddenInternal(), false);
QCOMPARE(Cursor::pos(), QPoint(1, 50));
// now let's reserve the client for each of the edges, in the end for the right one
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricTop);
s->reserve(&client, KWin::ElectricBottom);
QCOMPARE(client.isHiddenInternal(), true);
// corners shouldn't get reserved
s->reserve(&client, KWin::ElectricTopLeft);
QCOMPARE(client.isHiddenInternal(), false);
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricTopRight);
QCOMPARE(client.isHiddenInternal(), false);
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricBottomRight);
QCOMPARE(client.isHiddenInternal(), false);
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricBottomLeft);
QCOMPARE(client.isHiddenInternal(), false);
// now finally reserve on right one
client.setHiddenInternal(true);
s->reserve(&client, KWin::ElectricRight);
QCOMPARE(client.isHiddenInternal(), true);
// now let's emulate the removal of a Client through Workspace
emit workspace()->clientRemoved(&client);
for (auto e : s->findChildren<Edge*>()) {
QVERIFY(!e->client());
}
QCOMPARE(client.isHiddenInternal(), true);
// now let's try to trigger the client showing with the check method instead of enter notify
s->reserve(&client, KWin::ElectricTop);
QCOMPARE(client.isHiddenInternal(), true);
Cursor::setPos(50, 0);
s->check(QPoint(50, 0), QDateTime::currentDateTime());
QCOMPARE(client.isHiddenInternal(), false);
QCOMPARE(Cursor::pos(), QPoint(50, 1));
// unreserve by setting to none edge
s->reserve(&client, KWin::ElectricNone);
// check on previous edge again, should fail
client.setHiddenInternal(true);
Cursor::setPos(50, 0);
s->check(QPoint(50, 0), QDateTime::currentDateTime());
QCOMPARE(client.isHiddenInternal(), true);
QCOMPARE(Cursor::pos(), QPoint(50, 0));
// set to windows can cover
client.setGeometry(screens()->geometry());
client.setHiddenInternal(false);
client.setKeepBelow(true);
s->reserve(&client, KWin::ElectricLeft);
QCOMPARE(client.keepBelow(), true);
QCOMPARE(client.isHiddenInternal(), false);
xcb_enter_notify_event_t event2;
Cursor::setPos(0, 50);
event2.root_x = 0;
event2.root_y = 50;
event2.event_x = 0;
event2.event_y = 50;
event2.root = XCB_WINDOW_NONE;
event2.child = XCB_WINDOW_NONE;
event2.event = s->windows().first();
event2.same_screen_focus = 1;
event2.time = QDateTime::currentMSecsSinceEpoch();
QVERIFY(s->isEntered(&event2));
QCOMPARE(client.keepBelow(), false);
QCOMPARE(client.isHiddenInternal(), false);
QCOMPARE(Cursor::pos(), QPoint(1, 50));
}
QTEST_MAIN(TestScreenEdges)
#include "test_screen_edges.moc"