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
This commit is contained in:
Martin Gräßlin 2016-09-15 21:03:40 +02:00
parent 73dd1b9e46
commit dd3c6d6cc2
9 changed files with 200 additions and 22 deletions

View file

@ -580,6 +580,13 @@ public:
QRect inputGeometry() const override; 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 // TODO: remove boolean trap
static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false);

View file

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/ *********************************************************************/
#include "kwin_wayland_test.h" #include "kwin_wayland_test.h"
#include "platform.h" #include "platform.h"
#include "cursor.h"
#include "shell_client.h" #include "shell_client.h"
#include "screens.h" #include "screens.h"
#include "wayland_server.h" #include "wayland_server.h"
@ -53,6 +54,8 @@ private Q_SLOTS:
void testAcceptsFocus(); void testAcceptsFocus();
void testDesktopIsOpaque(); void testDesktopIsOpaque();
void testPanelWindowsCanCover_data();
void testPanelWindowsCanCover();
void testOSDPlacement(); void testOSDPlacement();
void testPanelTypeHasStrut_data(); void testPanelTypeHasStrut_data();
void testPanelTypeHasStrut(); void testPanelTypeHasStrut();
@ -81,6 +84,8 @@ void PlasmaSurfaceTest::init()
m_compositor = Test::waylandCompositor(); m_compositor = Test::waylandCompositor();
m_shell = Test::waylandShell(); m_shell = Test::waylandShell();
m_plasmaShell = Test::waylandPlasmaShell(); m_plasmaShell = Test::waylandPlasmaShell();
KWin::Cursor::setPos(640, 512);
} }
void PlasmaSurfaceTest::cleanup() void PlasmaSurfaceTest::cleanup()
@ -283,5 +288,85 @@ void PlasmaSurfaceTest::testPanelTypeHasStrut()
QTEST(c->layer(), "expectedLayer"); QTEST(c->layer(), "expectedLayer");
} }
void PlasmaSurfaceTest::testPanelWindowsCanCover_data()
{
QTest::addColumn<QRect>("panelGeometry");
QTest::addColumn<QRect>("windowGeometry");
QTest::addColumn<QPoint>("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> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<QObject> shellSurface(Test::createShellSurface(Test::ShellSurfaceType::WlShell, surface.data()));
QVERIFY(!shellSurface.isNull());
QScopedPointer<PlasmaShellSurface> 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<Surface> surface2(Test::createSurface());
QVERIFY(!surface2.isNull());
QScopedPointer<QObject> 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) WAYLANDTEST_MAIN(PlasmaSurfaceTest)
#include "plasma_surface_test.moc" #include "plasma_surface_test.moc"

View file

@ -47,6 +47,7 @@ public:
void setHiddenInternal(bool set); void setHiddenInternal(bool set);
void setGeometry(const QRect &rect); void setGeometry(const QRect &rect);
void setKeepBelow(bool); void setKeepBelow(bool);
virtual void showOnScreenEdge() = 0;
Q_SIGNALS: Q_SIGNALS:
void geometryChanged(); void geometryChanged();

View file

@ -36,7 +36,7 @@ public:
virtual ~Client(); virtual ~Client();
bool isResize() const; bool isResize() const;
void showOnScreenEdge(); void showOnScreenEdge() override;
}; };

View file

@ -329,7 +329,7 @@ public:
* Restores the Client after it had been hidden due to show on screen edge functionality. * 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. * In addition the property gets deleted so that the Client knows that it is visible again.
**/ **/
void showOnScreenEdge(); void showOnScreenEdge() override;
static void cleanupX11(); static void cleanupX11();

View file

@ -555,13 +555,7 @@ ScreenEdges::ScreenEdges(QObject *parent)
QWidget w; QWidget w;
m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6; m_cornerOffset = (w.physicalDpiX() + w.physicalDpiY() + 5) / 6;
connect(workspace(), &Workspace::clientRemoved, this, [this](KWin::AbstractClient *c) { connect(workspace(), &Workspace::clientRemoved, this, &ScreenEdges::deleteEdgeForClient);
Client *client = qobject_cast<Client*>(c);
if (!client) {
return;
}
deleteEdgeForClient(client);
});
} }
ScreenEdges::~ScreenEdges() 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; bool hadBorder = false;
auto it = m_edges.begin(); 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 y = 0;
int x = 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(); auto it = m_edges.begin();
while (it != m_edges.end()) { while (it != m_edges.end()) {

View file

@ -43,7 +43,7 @@ class QMouseEvent;
namespace KWin { namespace KWin {
class Client; class AbstractClient;
class ScreenEdges; class ScreenEdges;
class KWIN_EXPORT Edge : public QObject class KWIN_EXPORT Edge : public QObject
@ -70,8 +70,8 @@ public:
void startApproaching(); void startApproaching();
void stopApproaching(); void stopApproaching();
bool isApproaching() const; bool isApproaching() const;
void setClient(Client *client); void setClient(AbstractClient *client);
Client *client() const; AbstractClient *client() const;
const QRect &geometry() const; const QRect &geometry() const;
/** /**
@ -129,7 +129,7 @@ private:
int m_lastApproachingFactor; int m_lastApproachingFactor;
bool m_blocked; bool m_blocked;
bool m_pushBackBlocked; 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 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) * @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. * 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 * @param reserve indicated weather desktop switching should be reserved or unreseved
@ -339,8 +339,8 @@ private:
ElectricBorderAction actionForEdge(Edge *edge) const; ElectricBorderAction actionForEdge(Edge *edge) const;
bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp); bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp);
bool handleDndNotify(xcb_window_t window, const QPoint &point); bool handleDndNotify(xcb_window_t window, const QPoint &point);
void createEdgeForClient(Client *client, ElectricBorder border); void createEdgeForClient(AbstractClient *client, ElectricBorder border);
void deleteEdgeForClient(Client *client); void deleteEdgeForClient(AbstractClient *client);
bool m_desktopSwitching; bool m_desktopSwitching;
bool m_desktopSwitchingMovingClients; bool m_desktopSwitchingMovingClients;
QSize m_cursorPushBackDistance; QSize m_cursorPushBackDistance;
@ -452,12 +452,12 @@ inline bool Edge::isBlocked() const
return m_blocked; return m_blocked;
} }
inline void Edge::setClient(Client *client) inline void Edge::setClient(AbstractClient *client)
{ {
m_client = client; m_client = client;
} }
inline Client *Edge::client() const inline AbstractClient *Edge::client() const
{ {
return m_client; return m_client;
} }

View file

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "cursor.h" #include "cursor.h"
#include "deleted.h" #include "deleted.h"
#include "placement.h" #include "placement.h"
#include "screenedge.h"
#include "screens.h" #include "screens.h"
#include "wayland_server.h" #include "wayland_server.h"
#include "workspace.h" #include "workspace.h"
@ -407,6 +408,7 @@ void ShellClient::markAsMapped()
if (shouldExposeToWindowManagement()) { if (shouldExposeToWindowManagement()) {
setupWindowManagementInterface(); setupWindowManagementInterface();
} }
updateShowOnScreenEdge();
} }
void ShellClient::createDecoration(const QRect &oldGeom) void ShellClient::createDecoration(const QRect &oldGeom)
@ -1018,12 +1020,15 @@ void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface
connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition);
connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole);
connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this,
[] { [this] {
updateShowOnScreenEdge();
workspace()->updateClientArea(); workspace()->updateClientArea();
} }
); );
updatePosition(); updatePosition();
updateRole(); updateRole();
updateShowOnScreenEdge();
connect(this, &ShellClient::geometryChanged, this, &ShellClient::updateShowOnScreenEdge);
setSkipTaskbar(surface->skipTaskbar()); setSkipTaskbar(surface->skipTaskbar());
connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { 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 bool ShellClient::isInitialPositionSet() const
{ {
if (m_plasmaShellSurface) { if (m_plasmaShellSurface) {
@ -1243,4 +1321,14 @@ void ShellClient::placeIn(QRect &area)
setGeometryRestore(geometry()); 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
}
} }

View file

@ -127,6 +127,8 @@ public:
bool setupCompositing() override; bool setupCompositing() override;
void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override;
void showOnScreenEdge() override;
// TODO: const-ref // TODO: const-ref
void placeIn(QRect &area); void placeIn(QRect &area);
@ -164,6 +166,7 @@ private:
void setTransient(); void setTransient();
bool shouldExposeToWindowManagement(); bool shouldExposeToWindowManagement();
KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const;
void updateShowOnScreenEdge();
static void deleteClient(ShellClient *c); static void deleteClient(ShellClient *c);
KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::ShellSurfaceInterface *m_shellSurface;