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;
/**
* 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);

View file

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#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<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)
#include "plasma_surface_test.moc"

View file

@ -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();

View file

@ -36,7 +36,7 @@ public:
virtual ~Client();
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.
* In addition the property gets deleted so that the Client knows that it is visible again.
**/
void showOnScreenEdge();
void showOnScreenEdge() override;
static void cleanupX11();

View file

@ -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<Client*>(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()) {

View file

@ -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 &timestamp);
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;
}

View file

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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
}
}

View file

@ -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;