diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 20cb800dd1..e76359d167 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -205,3 +205,37 @@ target_link_libraries(testScreens
add_test(kwin_testScreens testScreens)
ecm_mark_as_test(testScreens)
+
+########################################################
+# Test ScreenEdges
+########################################################
+set( testScreenEdges_SRCS
+ test_screen_edges.cpp
+ mock_client.cpp
+ mock_screens.cpp
+ mock_workspace.cpp
+ ../atoms.cpp
+ ../screens.cpp
+ ../screenedge.cpp
+ ../virtualdesktops.cpp
+)
+kconfig_add_kcfg_files(testScreenEdges_SRCS ../settings.kcfgc)
+qt5_add_dbus_interface( testScreenEdges_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../org.freedesktop.ScreenSaver.xml screenlocker_interface)
+
+add_executable( testScreenEdges ${testScreenEdges_SRCS})
+target_include_directories(testScreenEdges BEFORE PRIVATE ./)
+target_link_libraries(testScreenEdges
+ Qt5::DBus
+ Qt5::Test
+ Qt5::X11Extras
+ KF5::ConfigCore
+ KF5::ConfigGui
+ KF5::I18n
+ KF5::GlobalAccel
+ KF5::WindowSystem
+ KF5::Service
+ XCB::XCB
+)
+
+add_test(kwin_testScreenEdges testScreenEdges)
+ecm_mark_as_test(testScreenEdges)
diff --git a/autotests/mock_client.cpp b/autotests/mock_client.cpp
index ce9546ebe1..743a768584 100644
--- a/autotests/mock_client.cpp
+++ b/autotests/mock_client.cpp
@@ -26,6 +26,9 @@ Client::Client(QObject *parent)
: QObject(parent)
, m_active(false)
, m_screen(0)
+ , m_fullscreen(false)
+ , m_hiddenInternal(false)
+ , m_geometry()
{
}
@@ -57,4 +60,39 @@ int Client::screen() const
return m_screen;
}
+void Client::showOnScreenEdge()
+{
+}
+
+void Client::setFullScreen(bool set)
+{
+ m_fullscreen = set;
+}
+
+bool Client::isFullScreen() const
+{
+ return m_fullscreen;
+}
+
+bool Client::isHiddenInternal() const
+{
+ return m_hiddenInternal;
+}
+
+void Client::setHiddenInternal(bool set)
+{
+ m_hiddenInternal = set;
+}
+
+void Client::setGeometry(const QRect &rect)
+{
+ m_geometry = rect;
+ emit geometryChanged();
+}
+
+QRect Client::geometry() const
+{
+ return m_geometry;
+}
+
}
diff --git a/autotests/mock_client.h b/autotests/mock_client.h
index 513d141c44..ee267ffba1 100644
--- a/autotests/mock_client.h
+++ b/autotests/mock_client.h
@@ -21,6 +21,7 @@ along with this program. If not, see .
#define KWIN_MOCK_CLIENT_H
#include
+#include
namespace KWin
{
@@ -35,13 +36,27 @@ public:
int screen() const;
bool isOnScreen(int screen) const;
bool isActive() const;
+ bool isFullScreen() const;
+ bool isHiddenInternal() const;
+ QRect geometry() const;
void setActive(bool active);
void setScreen(int screen);
+ void setFullScreen(bool set);
+ void setHiddenInternal(bool set);
+ void setGeometry(const QRect &rect);
+
+ void showOnScreenEdge();
+
+Q_SIGNALS:
+ void geometryChanged();
private:
bool m_active;
int m_screen;
+ bool m_fullscreen;
+ bool m_hiddenInternal;
+ QRect m_geometry;
};
}
diff --git a/autotests/mock_workspace.cpp b/autotests/mock_workspace.cpp
index 5e3941ae78..05a46cf108 100644
--- a/autotests/mock_workspace.cpp
+++ b/autotests/mock_workspace.cpp
@@ -28,6 +28,8 @@ Workspace *MockWorkspace::s_self = nullptr;
MockWorkspace::MockWorkspace(QObject *parent)
: QObject(parent)
, m_activeClient(nullptr)
+ , m_movingClient(nullptr)
+ , m_showingDesktop(false)
{
s_self = this;
}
@@ -47,5 +49,32 @@ void MockWorkspace::setActiveClient(Client *c)
m_activeClient = c;
}
+Client *MockWorkspace::getMovingClient() const
+{
+ return m_movingClient;
+}
+
+void MockWorkspace::setMovingClient(Client *c)
+{
+ m_movingClient = c;
+}
+
+void MockWorkspace::setShowingDesktop(bool showing)
+{
+ m_showingDesktop = showing;
+}
+
+bool MockWorkspace::showingDesktop() const
+{
+ return m_showingDesktop;
+}
+
+QRect MockWorkspace::clientArea(clientAreaOption, int screen, int desktop) const
+{
+ Q_UNUSED(screen)
+ Q_UNUSED(desktop)
+ return QRect();
+}
+
}
diff --git a/autotests/mock_workspace.h b/autotests/mock_workspace.h
index 402d83e6fd..40a770c542 100644
--- a/autotests/mock_workspace.h
+++ b/autotests/mock_workspace.h
@@ -21,6 +21,7 @@ along with this program. If not, see .
#define KWIN_MOCK_WORKSPACE_H
#include
+#include
namespace KWin
{
@@ -37,13 +38,23 @@ public:
explicit MockWorkspace(QObject *parent = nullptr);
virtual ~MockWorkspace();
Client *activeClient() const;
+ Client *getMovingClient() const;
+ void setShowingDesktop(bool showing);
+ bool showingDesktop() const;
+ QRect clientArea(clientAreaOption, int screen, int desktop) const;
void setActiveClient(Client *c);
+ void setMovingClient(Client *c);
static Workspace *self();
+Q_SIGNALS:
+ void clientRemoved(KWin::Client*);
+
private:
Client *m_activeClient;
+ Client *m_movingClient;
+ bool m_showingDesktop;
static Workspace *s_self;
};
@@ -53,6 +64,11 @@ Workspace *MockWorkspace::self()
return s_self;
}
+inline Workspace *workspace()
+{
+ return Workspace::self();
+}
+
}
#endif
diff --git a/autotests/test_screen_edges.cpp b/autotests/test_screen_edges.cpp
new file mode 100644
index 0000000000..36a213d3eb
--- /dev/null
+++ b/autotests/test_screen_edges.cpp
@@ -0,0 +1,676 @@
+/********************************************************************
+KWin - the KDE window manager
+This file is part of the KDE project.
+
+Copyright (C) 2014 Martin Gräßlin
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+// 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
+// Qt
+#include
+// xcb
+#include
+Q_DECLARE_METATYPE(KWin::ElectricBorder)
+
+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 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 testPushBack_data();
+ void testPushBack();
+ void testFullScreenBlocking();
+};
+
+void TestScreenEdges::initTestCase()
+{
+ KWin::atoms = new KWin::Atoms;
+ qRegisterMetaType();
+}
+
+void TestScreenEdges::cleanupTestCase()
+{
+ delete KWin::atoms;
+}
+
+void TestScreenEdges::init()
+{
+ using namespace KWin;
+ 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();
+}
+
+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 edges = s->findChildren(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();
+ 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 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 edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly);
+ QCOMPARE(edges.size(), 8);
+ for (auto e : edges) {
+ QVERIFY(e->isReserved());
+ }
+
+ static_cast(screens())->setGeometries(QList{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(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(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(screens())->setGeometries(QList{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 edges = s->findChildren(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;
+ });
+ 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(*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(100);
+ 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(), ElectricLeft);
+ QCOMPARE(spy.last().first().value(), 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
+ event.time = QDateTime::currentMSecsSinceEpoch();
+ QVERIFY(s->isEntered(&event));
+ QCOMPARE(spy.count(), 3);
+ QCOMPARE(spy.at(0).first().value(), ElectricLeft);
+ QCOMPARE(spy.at(1).first().value(), ElectricLeft);
+ QCOMPARE(spy.at(2).first().value(), 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(QString(), Qt::FindDirectChildrenOnly)) {
+ QVERIFY(!e->isReserved());
+ }
+}
+
+void TestScreenEdges::testPushBack_data()
+{
+ QTest::addColumn("border");
+ QTest::addColumn("pushback");
+ QTest::addColumn("trigger");
+ QTest::addColumn("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");
+}
+
+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()) {
+ 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
+ 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());
+}
+
+QTEST_MAIN(TestScreenEdges)
+#include "test_screen_edges.moc"
diff --git a/screenedge.cpp b/screenedge.cpp
index 5797bba18d..75035540f1 100644
--- a/screenedge.cpp
+++ b/screenedge.cpp
@@ -31,16 +31,18 @@ along with this program. If not, see .
// KWin
#include "atoms.h"
-#include "client.h"
+#include
#include "cursor.h"
#include "input.h"
#include "main.h"
#include "screens.h"
#include "utils.h"
-#include "workspace.h"
+#include
#include "virtualdesktops.h"
// DBus generated
#include "screenlocker_interface.h"
+// frameworks
+#include
// Qt
#include
#include
@@ -48,6 +50,7 @@ along with this program. If not, see .
#include
#include
#include
+#include
namespace KWin {
@@ -281,12 +284,14 @@ void Edge::switchDesktop(const QPoint &cursorPos)
if (desktop != interimDesktop)
pos.setY(OFFSET);
}
+#ifndef KWIN_UNIT_TEST
if (Client *c = Workspace::self()->getMovingClient()) {
if (c->rules()->checkDesktop(desktop) != int(desktop)) {
// user attempts to move a client to another desktop where it is ruleforced to not be
return;
}
}
+#endif
vds->setCurrent(desktop);
if (vds->current() != oldDesktop) {
m_pushBackBlocked = true;