From dd3c6d6cc24cb215b91b52d9ac0be023a8f48633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 15 Sep 2016 21:03:40 +0200 Subject: [PATCH] Implement show on screen edge for Wayland Summary: This change ports ScreenEdges to operate on AbstractClient instead of Client. For this AbstractClient gained a new pure virtual method showOnScreenEdge which is also implemented in ShellClient. In ShellClient the functionality is bound for the case windows can cover a panel. If triggered the panel gets raised again. The auto hiding panel, though, is not yet implemented. For that the protocol needs to be adjusted to give a hint to the compositor when to hide and hint back to the panel when it was shown. This needs a change in KWayland and thus is not 5.8 material. Test Plan: See added test case Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D2793 --- abstract_client.h | 7 ++ autotests/integration/plasma_surface_test.cpp | 85 ++++++++++++++++++ autotests/mock_abstract_client.h | 1 + autotests/mock_client.h | 2 +- client.h | 2 +- screenedge.cpp | 14 +-- screenedge.h | 18 ++-- shell_client.cpp | 90 ++++++++++++++++++- shell_client.h | 3 + 9 files changed, 200 insertions(+), 22 deletions(-) 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;