diff --git a/abstract_client.h b/abstract_client.h
index 142743dcf9..bfe5f6d3e8 100644
--- a/abstract_client.h
+++ b/abstract_client.h
@@ -580,6 +580,13 @@ public:
QRect inputGeometry() const override;
+ /**
+ * Restores the AbstractClient after it had been hidden due to show on screen edge functionality.
+ * The AbstractClient also gets raised (e.g. Panel mode windows can cover) and the AbstractClient
+ * gets informed in a window specific way that it is shown and raised again.
+ **/
+ virtual void showOnScreenEdge() = 0;
+
// TODO: remove boolean trap
static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false);
diff --git a/autotests/integration/plasma_surface_test.cpp b/autotests/integration/plasma_surface_test.cpp
index 07a3d10839..9b96dadfc9 100644
--- a/autotests/integration/plasma_surface_test.cpp
+++ b/autotests/integration/plasma_surface_test.cpp
@@ -19,6 +19,7 @@ along with this program. If not, see .
*********************************************************************/
#include "kwin_wayland_test.h"
#include "platform.h"
+#include "cursor.h"
#include "shell_client.h"
#include "screens.h"
#include "wayland_server.h"
@@ -53,6 +54,8 @@ private Q_SLOTS:
void testAcceptsFocus();
void testDesktopIsOpaque();
+ void testPanelWindowsCanCover_data();
+ void testPanelWindowsCanCover();
void testOSDPlacement();
void testPanelTypeHasStrut_data();
void testPanelTypeHasStrut();
@@ -81,6 +84,8 @@ void PlasmaSurfaceTest::init()
m_compositor = Test::waylandCompositor();
m_shell = Test::waylandShell();
m_plasmaShell = Test::waylandPlasmaShell();
+
+ KWin::Cursor::setPos(640, 512);
}
void PlasmaSurfaceTest::cleanup()
@@ -283,5 +288,85 @@ void PlasmaSurfaceTest::testPanelTypeHasStrut()
QTEST(c->layer(), "expectedLayer");
}
+void PlasmaSurfaceTest::testPanelWindowsCanCover_data()
+{
+ QTest::addColumn("panelGeometry");
+ QTest::addColumn("windowGeometry");
+ QTest::addColumn("triggerPoint");
+
+ QTest::newRow("top-full-edge") << QRect(0, 0, 1280, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0);
+ QTest::newRow("top-left-edge") << QRect(0, 0, 1000, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0);
+ QTest::newRow("top-right-edge") << QRect(280, 0, 1000, 30) << QRect(1000, 0, 200, 300) << QPoint(1000, 0);
+ QTest::newRow("bottom-full-edge") << QRect(0, 994, 1280, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023);
+ QTest::newRow("bottom-left-edge") << QRect(0, 994, 1000, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023);
+ QTest::newRow("bottom-right-edge") << QRect(280, 994, 1000, 30) << QRect(1000, 724, 200, 300) << QPoint(1000, 1023);
+ QTest::newRow("left-full-edge") << QRect(0, 0, 30, 1024) << QRect(0, 0, 200, 300) << QPoint(0, 100);
+ QTest::newRow("left-top-edge") << QRect(0, 0, 30, 800) << QRect(0, 0, 200, 300) << QPoint(0, 100);
+ QTest::newRow("left-bottom-edge") << QRect(0, 200, 30, 824) << QRect(0, 0, 200, 300) << QPoint(0, 250);
+ QTest::newRow("right-full-edge") << QRect(1250, 0, 30, 1024) << QRect(1080, 0, 200, 300) << QPoint(1279, 100);
+ QTest::newRow("right-top-edge") << QRect(1250, 0, 30, 800) << QRect(1080, 0, 200, 300) << QPoint(1279, 100);
+ QTest::newRow("right-bottom-edge") << QRect(1250, 200, 30, 824) << QRect(1080, 0, 200, 300) << QPoint(1279, 250);
+}
+
+void PlasmaSurfaceTest::testPanelWindowsCanCover()
+{
+ // this test verifies the behavior of a panel with windows can cover
+ // triggering the screen edge should raise the panel.
+ QScopedPointer surface(Test::createSurface());
+ QVERIFY(!surface.isNull());
+ QScopedPointer shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data()));
+ QVERIFY(!shellSurface.isNull());
+ QScopedPointer plasmaSurface(m_plasmaShell->createSurface(surface.data()));
+ QVERIFY(!plasmaSurface.isNull());
+ plasmaSurface->setRole(PlasmaShellSurface::Role::Panel);
+ QFETCH(QRect, panelGeometry);
+ plasmaSurface->setPosition(panelGeometry.topLeft());
+ plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsCanCover);
+
+
+ // now render and map the window
+ auto panel = Test::renderAndWaitForShown(surface.data(), panelGeometry.size(), Qt::blue);
+
+ QVERIFY(panel);
+ QCOMPARE(panel->windowType(), NET::Dock);
+ QVERIFY(panel->isDock());
+ QCOMPARE(panel->geometry(), panelGeometry);
+ QCOMPARE(panel->hasStrut(), false);
+ QCOMPARE(workspace()->clientArea(MaximizeArea, 0, 0), QRect(0, 0, 1280, 1024));
+ QCOMPARE(panel->layer(), KWin::NormalLayer);
+
+ // create a Window
+ QScopedPointer surface2(Test::createSurface());
+ QVERIFY(!surface2.isNull());
+ QScopedPointer shellSurface2(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface2.data()));
+ QVERIFY(!shellSurface2.isNull());
+
+ QFETCH(QRect, windowGeometry);
+ auto c = Test::renderAndWaitForShown(surface2.data(), windowGeometry.size(), Qt::red);
+
+ QVERIFY(c);
+ QCOMPARE(c->windowType(), NET::Normal);
+ QVERIFY(c->isActive());
+ QCOMPARE(c->layer(), KWin::NormalLayer);
+ c->move(windowGeometry.topLeft());
+ QCOMPARE(c->geometry(), windowGeometry);
+
+ auto stackingOrder = workspace()->stackingOrder();
+ QCOMPARE(stackingOrder.count(), 2);
+ QCOMPARE(stackingOrder.first(), panel);
+ QCOMPARE(stackingOrder.last(), c);
+
+ QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
+ QVERIFY(stackingOrderChangedSpy.isValid());
+ // trigger screenedge
+ QFETCH(QPoint, triggerPoint);
+ KWin::Cursor::setPos(triggerPoint);
+ QCOMPARE(stackingOrderChangedSpy.count(), 1);
+ stackingOrder = workspace()->stackingOrder();
+ QCOMPARE(stackingOrder.count(), 2);
+ QCOMPARE(stackingOrder.first(), c);
+ QCOMPARE(stackingOrder.last(), panel);
+}
+
WAYLANDTEST_MAIN(PlasmaSurfaceTest)
#include "plasma_surface_test.moc"
diff --git a/autotests/mock_abstract_client.h b/autotests/mock_abstract_client.h
index c7922d3640..a88f04f06b 100644
--- a/autotests/mock_abstract_client.h
+++ b/autotests/mock_abstract_client.h
@@ -47,6 +47,7 @@ public:
void setHiddenInternal(bool set);
void setGeometry(const QRect &rect);
void setKeepBelow(bool);
+ virtual void showOnScreenEdge() = 0;
Q_SIGNALS:
void geometryChanged();
diff --git a/autotests/mock_client.h b/autotests/mock_client.h
index 4405f05c29..df62ecd62d 100644
--- a/autotests/mock_client.h
+++ b/autotests/mock_client.h
@@ -36,7 +36,7 @@ public:
virtual ~Client();
bool isResize() const;
- void showOnScreenEdge();
+ void showOnScreenEdge() override;
};
diff --git a/client.h b/client.h
index bad486bca2..89b31a15b3 100644
--- a/client.h
+++ b/client.h
@@ -329,7 +329,7 @@ public:
* Restores the Client after it had been hidden due to show on screen edge functionality.
* In addition the property gets deleted so that the Client knows that it is visible again.
**/
- void showOnScreenEdge();
+ void showOnScreenEdge() override;
static void cleanupX11();
diff --git a/screenedge.cpp b/screenedge.cpp
index 149abeca56..7e0f2d2263 100644
--- a/screenedge.cpp
+++ b/screenedge.cpp
@@ -555,13 +555,7 @@ ScreenEdges::ScreenEdges(QObject *parent)
QWidget w;
m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6;
- connect(workspace(), &Workspace::clientRemoved, this, [this](KWin::AbstractClient *c) {
- Client *client = qobject_cast(c);
- if (!client) {
- return;
- }
- deleteEdgeForClient(client);
- });
+ connect(workspace(), &Workspace::clientRemoved, this, &ScreenEdges::deleteEdgeForClient);
}
ScreenEdges::~ScreenEdges()
@@ -985,7 +979,7 @@ void ScreenEdges::unreserve(ElectricBorder border, QObject *object)
}
}
-void ScreenEdges::reserve(Client *client, ElectricBorder border)
+void ScreenEdges::reserve(AbstractClient *client, ElectricBorder border)
{
bool hadBorder = false;
auto it = m_edges.begin();
@@ -1014,7 +1008,7 @@ void ScreenEdges::reserve(Client *client, ElectricBorder border)
}
}
-void ScreenEdges::createEdgeForClient(Client *client, ElectricBorder border)
+void ScreenEdges::createEdgeForClient(AbstractClient *client, ElectricBorder border)
{
int y = 0;
int x = 0;
@@ -1087,7 +1081,7 @@ void ScreenEdges::createEdgeForClient(Client *client, ElectricBorder border)
}
}
-void ScreenEdges::deleteEdgeForClient(Client* c)
+void ScreenEdges::deleteEdgeForClient(AbstractClient* c)
{
auto it = m_edges.begin();
while (it != m_edges.end()) {
diff --git a/screenedge.h b/screenedge.h
index b4087073fc..0c76b06169 100644
--- a/screenedge.h
+++ b/screenedge.h
@@ -43,7 +43,7 @@ class QMouseEvent;
namespace KWin {
-class Client;
+class AbstractClient;
class ScreenEdges;
class KWIN_EXPORT Edge : public QObject
@@ -70,8 +70,8 @@ public:
void startApproaching();
void stopApproaching();
bool isApproaching() const;
- void setClient(Client *client);
- Client *client() const;
+ void setClient(AbstractClient *client);
+ AbstractClient *client() const;
const QRect &geometry() const;
/**
@@ -129,7 +129,7 @@ private:
int m_lastApproachingFactor;
bool m_blocked;
bool m_pushBackBlocked;
- Client *m_client;
+ AbstractClient *m_client;
};
/**
@@ -257,7 +257,7 @@ public:
* @param client The Client for which an Edge should be reserved
* @param border The border which the client wants to use, only proper borders are supported (no corners)
**/
- void reserve(KWin::Client *client, ElectricBorder border);
+ void reserve(KWin::AbstractClient *client, ElectricBorder border);
/**
* Reserve desktop switching for screen edges, if @p isToReserve is @c true. Unreserve otherwise.
* @param reserve indicated weather desktop switching should be reserved or unreseved
@@ -339,8 +339,8 @@ private:
ElectricBorderAction actionForEdge(Edge *edge) const;
bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp);
bool handleDndNotify(xcb_window_t window, const QPoint &point);
- void createEdgeForClient(Client *client, ElectricBorder border);
- void deleteEdgeForClient(Client *client);
+ void createEdgeForClient(AbstractClient *client, ElectricBorder border);
+ void deleteEdgeForClient(AbstractClient *client);
bool m_desktopSwitching;
bool m_desktopSwitchingMovingClients;
QSize m_cursorPushBackDistance;
@@ -452,12 +452,12 @@ inline bool Edge::isBlocked() const
return m_blocked;
}
-inline void Edge::setClient(Client *client)
+inline void Edge::setClient(AbstractClient *client)
{
m_client = client;
}
-inline Client *Edge::client() const
+inline AbstractClient *Edge::client() const
{
return m_client;
}
diff --git a/shell_client.cpp b/shell_client.cpp
index 1b4fd849c6..cdbc104b34 100644
--- a/shell_client.cpp
+++ b/shell_client.cpp
@@ -22,6 +22,7 @@ along with this program. If not, see .
#include "cursor.h"
#include "deleted.h"
#include "placement.h"
+#include "screenedge.h"
#include "screens.h"
#include "wayland_server.h"
#include "workspace.h"
@@ -407,6 +408,7 @@ void ShellClient::markAsMapped()
if (shouldExposeToWindowManagement()) {
setupWindowManagementInterface();
}
+ updateShowOnScreenEdge();
}
void ShellClient::createDecoration(const QRect &oldGeom)
@@ -1018,12 +1020,15 @@ void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface
connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition);
connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole);
connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this,
- [] {
+ [this] {
+ updateShowOnScreenEdge();
workspace()->updateClientArea();
}
);
updatePosition();
updateRole();
+ updateShowOnScreenEdge();
+ connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge);
setSkipTaskbar(surface->skipTaskbar());
connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] {
@@ -1031,6 +1036,79 @@ void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface
});
}
+void ShellClient::updateShowOnScreenEdge()
+{
+ if (!ScreenEdges::self()) {
+ return;
+ }
+ if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) {
+ ScreenEdges::self()->reserve(this, ElectricNone);
+ return;
+ }
+ if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide ||
+ m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) {
+ // screen edge API requires an edge, thus we need to figure out which edge the window borders
+ Qt::Edges edges;
+ for (int i = 0; i < screens()->count(); i++) {
+ const auto &screenGeo = screens()->geometry(i);
+ if (screenGeo.x() == geom.x()) {
+ edges |= Qt::LeftEdge;
+ }
+ if (screenGeo.x() + screenGeo.width() == geom.x() + geom.width()) {
+ edges |= Qt::RightEdge;
+ }
+ if (screenGeo.y() == geom.y()) {
+ edges |= Qt::TopEdge;
+ }
+ if (screenGeo.y() + screenGeo.height() == geom.y() + geom.height()) {
+ edges |= Qt::BottomEdge;
+ }
+ }
+ // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will
+ // also border the left and right edge
+ // let's remove such cases
+ if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) {
+ edges = edges & (~(Qt::LeftEdge | Qt::RightEdge));
+ }
+ if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) {
+ edges = edges & (~(Qt::TopEdge | Qt::BottomEdge));
+ }
+ // it's still possible that a panel borders two edges, e.g. bottom and left
+ // in that case the one which is sharing more with the edge wins
+ auto check = [this](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) {
+ if (edges.testFlag(horiz) && edges.testFlag(vert)) {
+ if (geom.width() >= geom.height()) {
+ return edges & ~horiz;
+ } else {
+ return edges & ~vert;
+ }
+ }
+ return edges;
+ };
+ edges = check(edges, Qt::LeftEdge, Qt::TopEdge);
+ edges = check(edges, Qt::LeftEdge, Qt::BottomEdge);
+ edges = check(edges, Qt::RightEdge, Qt::TopEdge);
+ edges = check(edges, Qt::RightEdge, Qt::BottomEdge);
+
+ ElectricBorder border = ElectricNone;
+ if (edges.testFlag(Qt::LeftEdge)) {
+ border = ElectricLeft;
+ }
+ if (edges.testFlag(Qt::RightEdge)) {
+ border = ElectricRight;
+ }
+ if (edges.testFlag(Qt::TopEdge)) {
+ border = ElectricTop;
+ }
+ if (edges.testFlag(Qt::BottomEdge)) {
+ border = ElectricBottom;
+ }
+ ScreenEdges::self()->reserve(this, border);
+ } else {
+ ScreenEdges::self()->reserve(this, ElectricNone);
+ }
+}
+
bool ShellClient::isInitialPositionSet() const
{
if (m_plasmaShellSurface) {
@@ -1243,4 +1321,14 @@ void ShellClient::placeIn(QRect &area)
setGeometryRestore(geometry());
}
+void ShellClient::showOnScreenEdge()
+{
+ if (!m_plasmaShellSurface || m_unmapped) {
+ return;
+ }
+ // TODO: handle show
+ workspace()->raiseClient(this);
+ // TODO: inform the client about being shown again
+}
+
}
diff --git a/shell_client.h b/shell_client.h
index 9fd06bec98..4a6f2a6424 100644
--- a/shell_client.h
+++ b/shell_client.h
@@ -127,6 +127,8 @@ public:
bool setupCompositing() override;
void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override;
+ void showOnScreenEdge() override;
+
// TODO: const-ref
void placeIn(QRect &area);
@@ -164,6 +166,7 @@ private:
void setTransient();
bool shouldExposeToWindowManagement();
KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const;
+ void updateShowOnScreenEdge();
static void deleteClient(ShellClient *c);
KWayland::Server::ShellSurfaceInterface *m_shellSurface;